Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2013-03-12 14:05:47 +01:00
commit a1691a08ba
91 changed files with 2935 additions and 3373 deletions

10
.gitignore vendored
View File

@ -5,14 +5,6 @@ include/all.h.pch
*.swp
*.gcda
*.gcno
testcases/testsuite-*
testcases/latest
testcases/Makefile
testcases/Makefile.old
testcases/.last_run_timings.json
testcases/_Inline
testcases/inc
testcases/META.yml
test.commands_parser
test.config_parser
*.output
@ -32,3 +24,5 @@ libi3.a
docs/*.pdf
docs/*.html
!/docs/refcard.html
i3-command-parser.stamp
i3-config-parser.stamp

View File

@ -10,9 +10,7 @@
│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │
│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │
│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │
│ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │
│ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │
│ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │
│ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │
│ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │
│ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │
│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │

103
RELEASE-NOTES-4.5 Normal file
View File

@ -0,0 +1,103 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.5 │
└──────────────────────────────┘
This is the i3 v4.5. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Most of the changes are cleanups and bugfixes. Due to cleanups, i3 no longer
depends on flex/bison at all. Furthermore, libev ≥ 4 is now a hard dependency
(libev < 4 is not supported anymore).
One important change to note is that moving windows to a different output will
no longer move focus to that output. If you want to have the old behavior,
modify the keybindings for moving in your configfile like this:
bindsym $mod+Shift+1 move workspace 1; workspace 1
┌────────────────────────────┐
│ Changes in v4.5 │
└────────────────────────────┘
• docs/hacking-howto: refer people to cr.i3wm.org
• docs/ipc: Adds Go IPC lib to the docs.
• docs/userguide: remove obsolete sentence about client.background
• docs/userguide: be explicit about assignment processing order
• docs/userguide: be more clear about the resize command arguments
• docs/userguide: fix typo: s/11x/11px/
• i3-dmenu-desktop: dont add “geany” if “Geany” is already present
• i3-dmenu-desktop: strip newlines from dmenu ≥ 4.4
• i3-dmenu-desktop: skip files with broken utf8 but warn about it
• i3-dmenu-desktop: skip broken files (no/empty Exec=) but warn about them
• i3-dmenu-desktop: List filenames of .desktop files
• i3-dmenu-desktop: remove %i from commandline
• i3-nagbar: Work around terminals not supporting -e with quoted arguments
• i3-nagbar: use the same font as configured for i3
• i3bar: set _NET_SYSTEM_TRAY_COLORS for symbolic icons (gtk3+)
• i3bar: dont use X11 borders to avoid overlapping in hide mode
• i3bar: separator color via config; separator width and on/off via ipc
• i3bar: Allow min_width of a block in i3bar to be a string
• i3-msg: parse command replies and display errors nicely if there were
errors
• Draw 1px tab separators left/right instead of 2px on the right only
• Render tree before destroying X11 containers upon unmap
• scratchpad show: move visible scratchpad window from another workspace to
focused workspace instead of doing nothing
• ignore MotionNotify events generated while warping the pointer
• Allow X11 servers which do not support the XKB extension.
• remove the urgency indicator when a window is closed
• wrap when moving containers to outputs with direction
• scratchpad_show: focus unfocused scratchpad window
• Split workspace instead of changing orientation
• scratchpad: always auto center on 'scratchpad show' if window hasn't been
repositioned by the user
• Add a new IPC event for changes on windows.
• config: accept “smart” as popup_during_fullscreen parameter
• Add support for _NET_WM_STATE_DEMANDS_ATTENTION.
• Obey WM_SIZE_HINTS's resize increments in floating mode
• Do not move focus if a container is moved across outputs
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• Ignore ConfigureRequests for scratchpad windows
• Correctly parse `move ... workspace *_on_output`
• i3bar: Set separator color properly when drawing
• Properly parse commands like “move workspace torrent”
• Handle nested transient popups properly
• Fix decoration rect size for windows without border
• parse outputs as "word", not "string", to ignore trailing whitespace
• fix crash when disabling output without any windows
• scratchpad: fix crash when moving last window of an invisible workspace
• fix coordinates of scratchpad windows on output changes
• call scratchpad_show() when focusing scratchpad windows via criteria
• fix continuous resize bug in floating mode, e.g. with xbmc
• fix “overlapping” --release key bindings
• fix IPC messages writes with low buffer sizes
• unregister as window manager before restarting (fixes a race condition)
• Fix bind[code|sym] --release
• remove superfluous #include <xcb/xcb_atom.h>
• Makefile: Repect AR environment variable
• i3-input: restore input focus on exit()
• Also draw right tab border for split containers
• Fix scrolling on a tabbed titlebar which contains split cons
• Correctly close floating windows
• handle MapRequests sent between i3 registering as a wm and handling events
• i3bar: fake DestroyNotify and send MANAGER ClientMessages to fix tray restarts
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Adrien Schildknecht, alex, András Mohari, Artem Shinkarov, badboy, bafain,
cradle, dcoppa, Donald, dRbiG, eeemsi, else, emias, f8l, Francesco Mazzoli,
jasper, joepd, Kacper Kowalik, Kai, knopwob, Marcos, Marius Muja, Mats,
MeanEYE, Merovius, oblique, paolo, phlux, Piotr S. Staszewski, pnutzh4x0r,
rasi, saurabhgeek92, Sebastian Rachuj, Sebastian Ullrich, slowpoke, Steven
Allen, supplantr, Tai-Lin Chu, Tucos, Vivien Didelot, xeen
-- Michael Stapelberg, 2013-03-12

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.4.1-0) unstable; urgency=low
* NOT YET RELEASED
-- Michael Stapelberg <stapelberg@debian.org> Wed, 12 Dec 2012 00:23:32 +0100
i3-wm (4.4-1) experimental; urgency=low
* New upstream release

4
debian/control vendored
View File

@ -14,9 +14,7 @@ Build-Depends: debhelper (>= 7.0.50~),
xmlto,
docbook-xml,
pkg-config,
libev-dev,
flex,
bison,
libev-dev (>= 1:4.04),
libyajl-dev,
libpcre3-dev,
libstartup-notification0-dev (>= 0.10),

View File

@ -1,7 +1,7 @@
Hacking i3: How To
==================
Michael Stapelberg <michael+i3@stapelberg.de>
July 2011
Michael Stapelberg <michael@i3wm.org>
February 2013
This document is intended to be the first thing you read before looking and/or
touching i3s source code. It should contain all important information to help
@ -28,7 +28,8 @@ In the case of i3, the tasks (and order of them) are the following:
the first client of X) and manage them (reparent them, create window
decorations, etc.)
. When new windows are created, manage them
. Handle the clients `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
. Handle the clients `_WM_STATE` property, but only `_WM_STATE_FULLSCREEN` and
`_NET_WM_STATE_DEMANDS_ATTENTION`
. Handle the clients `WM_NAME` property
. Handle the clients size hints to display them proportionally
. Handle the clients urgency hint
@ -947,31 +948,20 @@ For a short introduction into using git, see
http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
http://git-scm.com/documentation
When you want to send a patch because you fixed a bug or implemented a cool
feature (please talk to us before working on features to see whether they are
maybe already implemented, not possible for some some reason, or dont fit
into the concept), please use git to create a patchfile.
Please talk to us before working on new features to see whether they will be
accepted. There are a few things which we dont want to see in i3, e.g. a
command which will focus windows in an alt+tab like way.
First of all, update your working copy to the latest version of the master
branch:
When working on bugfixes, please make sure you mention that you are working on
it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no
bugreport yet, please create one.
--------
git pull
--------
After you are done, please submit your work for review at http://cr.i3wm.org/
Afterwards, make the necessary changes for your bugfix/feature. Then, review
the changes using +git diff+ (you might want to enable colors in the diff using
+git config diff.color auto+). When you are definitely done, use +git commit
-a+ to commit all changes youve made.
Then, use the following command to generate a patchfile which we can directly
apply to the branch, preserving your commit message and name:
-----------------------
git format-patch origin
-----------------------
Just send us the generated file via email.
Do not send emails to the mailing list or any author directly, and dont submit
them in the bugtracker, since all reviews should be done in public at
http://cr.i3wm.org/. In order to make your review go as fast as possible, you
could have a look at previous reviews and see what the common mistakes are.
== Thought experiments

View File

@ -30,7 +30,7 @@ $parser->html_header_before_title(
<link rel="icon" type="image/png" href="/favicon.png">
<meta charset="utf-8">
<meta name="generator" content="Pod::Simple::HTML">
<meta name="description" content="i3 Perl documentation (testsuite)">
<meta name="description" content="i3 Perl documentation">
<link rel="stylesheet" href="http://i3wm.org/css/style.css" type="text/css" />
<style type="text/css">
.pod pre {
@ -81,7 +81,7 @@ $parser->html_header_after_title(
</ul>
<br style="clear: both">
<div id="content" class="pod">
<h1>i3 Perl documentation (testsuite)</h1>
<h1>i3 Perl documentation</h1>
EOF
);

View File

@ -140,6 +140,10 @@ min_width::
will be padded to the left and/or the right side, according to the +align+
key. This is useful when you want to prevent the whole status line to shift
when value take more or less space between each iteration.
The value can also be a string. In this case, the width of the text given
by +min_width+ determines the minimum width of the block. This is useful
when you want to set a sensible minimum width regardless of which font you
are using, and at what particular size.
align::
Align text on the +center+ (default), +right+ or +left+ of the block, when
the minimum width of the latter, specified by the +min_width+ key, is not
@ -154,6 +158,16 @@ urgent::
A boolean which specifies whether the current value is urgent. Examples
are battery charge values below 1 percent or no more available disk
space (for non-root users). The presentation of urgency is up to i3bar.
separator::
A boolean which specifies whether a separator line should be drawn
after this block. The default is true, meaning the separator line will
be drawn. Note that if you disable the separator line, there will still
be a gap after the block, unless you also use +separator_block_width+.
separator_block_width::
The amount of pixels to leave blank after the block. In the middle of
this gap, a separator line will be drawn unless +separator+ is
disabled. Normally, you want to set this to an odd value (the default
is 9 pixels), since the separator line is drawn in the middle.
If you want to put in your own entries into a block, prefix the key with an
underscore (_). i3bar will ignore all keys it doesnt understand, and prefixing
@ -168,6 +182,17 @@ of the i3bar protocol.
}
------------------------------------------
In the following example, the longest (widest) possible value of the block is
used to set the minimum width:
------------------------------------------
{
"full_text": "CPU 4%",
"min_width": "CPU 100%",
"align": "left"
}
------------------------------------------
An example of a block which uses all possible entries follows:
*Example*:
@ -180,6 +205,8 @@ An example of a block which uses all possible entries follows:
"align": "right",
"urgent": false,
"name": "ethernet",
"instance": "eth0"
"instance": "eth0",
"separator": true,
"separator_block_width": 9
}
------------------------------------------

View File

@ -507,6 +507,8 @@ background::
Background color of the bar.
statusline::
Text color to be used for the statusline.
separator::
Text color to be used for the separator.
focused_workspace_text/focused_workspace_bg::
Text color/background color for a workspace button when the workspace
has focus.
@ -621,6 +623,9 @@ output (1)::
outputs, CRTCs or output properties).
mode (2)::
Sent whenever i3 changes its binding mode.
window (3)::
Sent when a client's window is successfully reparented (that is when i3
has finished fitting it into a container).
*Example:*
--------------------------------------------------------------------
@ -694,6 +699,30 @@ mode is simply named default.
{ "change": "default" }
---------------------------
=== window event
This event consists of a single serialized map containing a property
+change (string)+ which currently can indicate only that a new window
has been successfully reparented (the value will be "new").
Additionally a +container (object)+ field will be present, which consists
of the window's parent container. Be aware that the container will hold
the initial name of the newly reparented window (e.g. if you run urxvt
with a shell that changes the title, you will still at this point get the
window title as "urxvt").
*Example:*
---------------------------
{
"change": "new",
"container": {
"id": 35569536,
"type": 2,
...
}
}
---------------------------
== See also (existing libraries)
[[libraries]]
@ -712,3 +741,5 @@ Perl::
Python::
* https://github.com/whitelynx/i3ipc
* https://github.com/ziberna/i3-py (includes higher-level features)
Go::
* https://github.com/proxypoke/i3ipc

View File

@ -1,7 +1,7 @@
i3 Users Guide
===============
Michael Stapelberg <michael@i3wm.org>
August 2012
February 2013
This document contains all the information you need to configure and use the i3
window manager. If it does not, please check http://faq.i3wm.org/ first, then
@ -324,7 +324,7 @@ font pango:[family list] [style options] [size]
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
font pango:DejaVu Sans Mono 10
font pango:DejaVu Sans Mono, Terminus Bold Semi-Condensed 11
font pango:Terminus 11x
font pango:Terminus 11px
--------------------------------------------------------------
[[keybindings]]
@ -590,6 +590,10 @@ title change. As i3 will get the title as soon as the application maps the
window (mapping means actually displaying it on the screen), youd need to have
to match on 'Firefox' in this case.
Assignments are processed by i3 in the order in which they appear in the config
file. The first one which matches the window wins and later assignments are not
considered.
*Syntax*:
------------------------------------------------------------
assign <criteria> [→] workspace
@ -732,10 +736,7 @@ client.background color
-----------------------
Only clients that do not cover the whole area of this window expose the color
used to paint it. If you use a color other than black for your terminals, you
most likely want to set the client background color to the same color as your
terminal program's background color to avoid black gaps between the rendered
area of the terminal and the i3 border.
used to paint it.
Colors are in HTML hex format (#rrggbb), see the following example:
@ -1154,6 +1155,8 @@ background::
Background color of the bar.
statusline::
Text color to be used for the statusline.
separator::
Text color to be used for the separator.
focused_workspace::
Border, background and text color for a workspace button when the workspace
has focus.
@ -1175,6 +1178,7 @@ urgent_workspace::
colors {
background <color>
statusline <color>
separator <color>
colorclass <border> <background> <text>
}
@ -1186,6 +1190,7 @@ bar {
colors {
background #000000
statusline #ffffff
separator #666666
focused_workspace #4c7899 #285577 #ffffff
active_workspace #333333 #5f676a #ffffff
@ -1612,7 +1617,7 @@ bindsym $mod+r mode "resize"
Often when in a multi-monitor environment, you want to quickly jump to a
specific window. For example, while working on workspace 3 you may want to
jump to your mail client to email your boss that youve achieved some
important goal. Instead of figuring out how to navigate to your mailclient,
important goal. Instead of figuring out how to navigate to your mail client,
it would be more convenient to have a shortcut. You can use the +focus+ command
with criteria for that.

View File

@ -1,105 +0,0 @@
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
/*
* vim:ts=8:expandtab
*
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "cfgparse.tab.h"
int yycolumn = 1;
struct context {
int line_number;
char *line_copy;
char *compact_error;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
};
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
%}
EOL (\r?\n)
%s BINDCODE_COND
%s BIND_AWS_COND
%s BIND_A2WS_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; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
Mod1 { yylval.number = (1 << 3); return MODIFIER; }
Mod2 { yylval.number = (1 << 4); return MODIFIER; }
Mod3 { yylval.number = (1 << 5); return MODIFIER; }
Mod4 { yylval.number = (1 << 6); return MODIFIER; }
Mod5 { yylval.number = (1 << 7); return MODIFIER; }
Mode_switch { yylval.number = (1 << 8); return MODIFIER; }
$mod { yylval.number = (1 << 9); return TOKMODVAR; }
control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; }
{EOL} {
if (context->line_copy) {
free(context->line_copy);
context->line_copy = NULL;
}
context->line_number++;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
<BINDCODE_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

View File

@ -1,208 +0,0 @@
%{
/*
* vim:ts=4:sw=4:expandtab
*
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include "libi3.h"
extern Display *dpy;
struct context {
int line_number;
char *line_copy;
char *compact_error;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
char *result;
};
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
extern int yyparse(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
static struct context *context;
static xcb_connection_t *conn;
static xcb_key_symbols_t *keysyms;
/* We dont need yydebug for now, as we got decent error messages using
* yyerror(). Should you ever want to extend the parser, it might be handy
* to just comment it in again, so it stays here. */
//int yydebug = 1;
void yyerror(const char *error_message) {
fprintf(stderr, "\n");
fprintf(stderr, "CONFIG: %s\n", error_message);
fprintf(stderr, "CONFIG: line %d:\n",
context->line_number);
fprintf(stderr, "CONFIG: %s\n", context->line_copy);
fprintf(stderr, "CONFIG: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
fprintf(stderr, "^");
else fprintf(stderr, " ");
fprintf(stderr, "\n");
fprintf(stderr, "\n");
}
int yywrap() {
return 1;
}
char *rewrite_binding(const char *bindingline) {
char *result = NULL;
conn = xcb_connect(NULL, NULL);
if (conn == NULL || xcb_connection_has_error(conn)) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
keysyms = xcb_key_symbols_alloc(conn);
context = calloc(sizeof(struct context), 1);
yy_scan_string(bindingline);
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
result = context->result;
if (context->line_copy)
free(context->line_copy);
free(context);
xcb_key_symbols_free(keysyms);
xcb_disconnect(conn);
return result;
}
/* XXX: does not work for combinations of modifiers yet */
static char *modifier_to_string(int modifiers) {
//printf("should convert %d to string\n", modifiers);
if (modifiers == (1 << 3))
return strdup("$mod+");
else if (modifiers == ((1 << 3) | (1 << 0)))
return strdup("$mod+Shift+");
else if (modifiers == (1 << 9))
return strdup("$mod+");
else if (modifiers == ((1 << 9) | (1 << 0)))
return strdup("$mod+Shift+");
else if (modifiers == (1 << 0))
return strdup("Shift+");
else return strdup("");
}
/*
* Returns true if sym is bound to any key except for 'except_keycode' on the
* first four layers (normal, shift, mode_switch, mode_switch + shift).
*
*/
static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
xcb_keycode_t i,
min_keycode = xcb_get_setup(conn)->min_keycode,
max_keycode = xcb_get_setup(conn)->max_keycode;
for (i = min_keycode; i && i <= max_keycode; i++) {
if (i == except_keycode)
continue;
for (int level = 0; level < 4; level++) {
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
continue;
return true;
}
}
return false;
}
%}
%error-verbose
%lex-param { struct context *context }
%union {
int number;
char *string;
}
%token <number>NUMBER "<number>"
%token <string>STR "<string>"
%token TOKBINDCODE
%token TOKMODVAR "$mod"
%token MODIFIER "<modifier>"
%token TOKCONTROL "control"
%token TOKSHIFT "shift"
%token WHITESPACE "<whitespace>"
%%
lines: /* empty */
| lines WHITESPACE bindcode
| lines error
| lines bindcode
;
bindcode:
TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR
{
//printf("\tFound keycode binding mod%d with key %d and command %s\n", $<number>3, $4, $<string>6);
int level = 0;
if (($<number>3 & (1 << 0))) {
/* When shift is included, we really need to use the second-level
* symbol (upper-case). The lower-case symbol could be on a
* different key than the upper-case one (unlikely for letters, but
* more likely for special characters). */
level = 1;
/* Try to use the keysym on the first level (lower-case). In case
* this doesnt make it ambiguous (think of a keyboard layout
* having '1' on two different keys, but '!' only on keycode 10),
* well stick with the keysym of the first level.
*
* This reduces a lot of confusion for users who switch keyboard
* layouts from qwerty to qwertz or other slight variations of
* qwerty (yes, that happens quite often). */
KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0);
if (!keysym_used_on_other_key(sym, $4))
level = 0;
}
KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level);
char *str = XKeysymToString(sym);
char *modifiers = modifier_to_string($<number>3);
sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
free(modifiers);
}
;
binding_modifiers:
/* NULL */ { $<number>$ = 0; }
| binding_modifier
| binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
| binding_modifiers '+' { $<number>$ = $<number>1; }
;
binding_modifier:
MODIFIER { $<number>$ = $<number>1; }
| TOKMODVAR { $<number>$ = $<number>1; }
| TOKCONTROL { $<number>$ = (1 << 2); }
| TOKSHIFT { $<number>$ = (1 << 0); }
;

View File

@ -2,27 +2,18 @@ ALL_TARGETS += i3-config-wizard/i3-config-wizard
INSTALL_TARGETS += install-i3-config-wizard
CLEAN_TARGETS += clean-i3-config-wizard
i3_config_wizard_SOURCES_GENERATED = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c
i3_config_wizard_SOURCES := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c))
i3_config_wizard_SOURCES := $(wildcard i3-config-wizard/*.c)
i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h)
i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS)
i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS)
i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o)
i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o)
i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS)
i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS) i3-config-parser.stamp
echo "[i3-config-wizard] CC $<"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS)
echo "[i3-config-wizard] LEX $<"
$(FLEX) -i -o $@ $<
i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS)
echo "[i3-config-wizard] YACC $<"
$(BISON) --debug --verbose -b $(basename $< .y) -d $<
i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
echo "[i3-config-wizard] Link i3-config-wizard"
$(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_config_wizard_LIBS)
@ -34,4 +25,4 @@ install-i3-config-wizard: i3-config-wizard/i3-config-wizard
clean-i3-config-wizard:
echo "[i3-config-wizard] Clean"
rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot,tab.h,y.o}
rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.*

View File

@ -36,6 +36,7 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <assert.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -44,6 +45,7 @@
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
/* We need SYSCONFDIR for the path to the keycode config template, so raise an
* error if its not defined for whatever reason */
@ -68,6 +70,7 @@ enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
static char *config_path;
static uint32_t xcb_numlock_mask;
xcb_connection_t *conn;
static xcb_key_symbols_t *keysyms;
xcb_screen_t *root_screen;
static xcb_get_modifier_mapping_reply_t *modmap_reply;
static i3Font font;
@ -80,9 +83,342 @@ static xcb_key_symbols_t *symbols;
xcb_window_t root;
Display *dpy;
char *rewrite_binding(const char *bindingline);
static void finish();
#include "GENERATED_config_enums.h"
typedef struct token {
char *name;
char *identifier;
/* This might be __CALL */
cmdp_state next_state;
union {
uint16_t call_identifier;
} extra;
} cmdp_token;
typedef struct tokenptr {
cmdp_token *array;
int n;
} cmdp_token_ptr;
#include "GENERATED_config_tokens.h"
static cmdp_state state;
/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
static cmdp_state statelist[10] = { INITIAL };
/* NB: statelist_idx points to where the next entry will be inserted */
static int statelist_idx = 1;
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
/* 10 entries should be enough for everybody. */
static struct stack_entry stack[10];
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL &&
strcmp(stack[c].identifier, identifier) != 0)
continue;
if (stack[c].identifier == NULL) {
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.str = sstrdup(str);
stack[c].type = STACK_STR;
} else {
/* Append the value. */
char *prev = stack[c].val.str;
sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
free(prev);
}
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(1);
}
static void push_long(const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL)
continue;
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.num = num;
stack[c].type = STACK_LONG;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(1);
}
static const char *get_string(const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.str;
}
return NULL;
}
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
free(stack[c].val.str);
stack[c].identifier = NULL;
stack[c].val.str = NULL;
stack[c].val.num = 0;
}
}
/*
* Returns true if sym is bound to any key except for 'except_keycode' on the
* first four layers (normal, shift, mode_switch, mode_switch + shift).
*
*/
static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
xcb_keycode_t i,
min_keycode = xcb_get_setup(conn)->min_keycode,
max_keycode = xcb_get_setup(conn)->max_keycode;
for (i = min_keycode; i && i <= max_keycode; i++) {
if (i == except_keycode)
continue;
for (int level = 0; level < 4; level++) {
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
continue;
return true;
}
}
return false;
}
static char *next_state(const cmdp_token *token) {
cmdp_state _next_state = token->next_state;
if (token->next_state == __CALL) {
const char *modifiers = get_string("modifiers");
int keycode = atoi(get_string("key"));
int level = 0;
if (modifiers != NULL &&
strstr(modifiers, "Shift") != NULL) {
/* When shift is included, we really need to use the second-level
* symbol (upper-case). The lower-case symbol could be on a
* different key than the upper-case one (unlikely for letters, but
* more likely for special characters). */
level = 1;
/* Try to use the keysym on the first level (lower-case). In case
* this doesnt make it ambiguous (think of a keyboard layout
* having '1' on two different keys, but '!' only on keycode 10),
* well stick with the keysym of the first level.
*
* This reduces a lot of confusion for users who switch keyboard
* layouts from qwerty to qwertz or other slight variations of
* qwerty (yes, that happens quite often). */
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, 0);
if (!keysym_used_on_other_key(sym, keycode))
level = 0;
}
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, level);
char *str = XKeysymToString(sym);
const char *release = get_string("release");
char *res;
char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
char *comma;
while ((comma = strchr(modrep, ',')) != NULL) {
*comma = '+';
}
sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
clear_stack();
return res;
}
state = _next_state;
/* See if we are jumping back to a state in which we were in previously
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
for (int i = 0; i < statelist_idx; i++) {
if (statelist[i] != _next_state)
continue;
statelist_idx = i+1;
return NULL;
}
/* Otherwise, the state is new and we add it to the list */
statelist[statelist_idx++] = _next_state;
return NULL;
}
static char *rewrite_binding(const char *input) {
state = INITIAL;
statelist_idx = 1;
const char *walk = input;
const size_t len = strlen(input);
int c;
const cmdp_token *token;
char *result = NULL;
/* The "<=" operator is intentional: We also handle the terminating 0-byte
* explicitly by looking for an 'end' token. */
while ((walk - input) <= len) {
/* Skip whitespace before every token, newlines are relevant since they
* separate configuration directives. */
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
walk++;
//printf("remaining input: %s\n", walk);
cmdp_token_ptr *ptr = &(tokens[state]);
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL)
push_string(token->identifier, token->name + 1);
walk += strlen(token->name) - 1;
if ((result = next_state(token)) != NULL)
return result;
break;
}
continue;
}
if (strcmp(token->name, "number") == 0) {
/* Handle numbers. We only accept decimal numbers for now. */
char *end = NULL;
errno = 0;
long int num = strtol(walk, &end, 10);
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
(errno != 0 && num == 0))
continue;
/* No valid numbers found */
if (end == walk)
continue;
if (token->identifier != NULL)
push_long(token->identifier, num);
/* Set walk to the first non-number character */
walk = end;
if ((result = next_state(token)) != NULL)
return result;
break;
}
if (strcmp(token->name, "string") == 0 ||
strcmp(token->name, "word") == 0) {
const char *beginning = walk;
/* Handle quoted strings (or words). */
if (*walk == '"') {
beginning++;
walk++;
while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\'))
walk++;
} else {
if (token->name[0] == 's') {
while (*walk != '\0' && *walk != '\r' && *walk != '\n')
walk++;
} else {
/* For a word, the delimiters are white space (' ' or
* '\t'), closing square bracket (]), comma (,) and
* semicolon (;). */
while (*walk != ' ' && *walk != '\t' &&
*walk != ']' && *walk != ',' &&
*walk != ';' && *walk != '\r' &&
*walk != '\n' && *walk != '\0')
walk++;
}
}
if (walk != beginning) {
char *str = scalloc(walk-beginning + 1);
/* We copy manually to handle escaping of characters. */
int inpos, outpos;
for (inpos = 0, outpos = 0;
inpos < (walk-beginning);
inpos++, outpos++) {
/* We only handle escaped double quotes to not break
* backwards compatibility with people using \w in
* regular expressions etc. */
if (beginning[inpos] == '\\' && beginning[inpos+1] == '"')
inpos++;
str[outpos] = beginning[inpos];
}
if (token->identifier)
push_string(token->identifier, str);
free(str);
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
walk++;
if ((result = next_state(token)) != NULL)
return result;
break;
}
}
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
if ((result = next_state(token)) != NULL)
return result;
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
// TODO: make this testable
walk++;
break;
}
}
}
}
return NULL;
}
/*
* Having verboselog() and errorlog() is necessary when using libi3.
*
@ -466,6 +802,7 @@ int main(int argc, char *argv[]) {
xcb_connection_has_error(conn))
errx(1, "Cannot open display\n");
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_cookie_t modmap_cookie;
modmap_cookie = xcb_get_modifier_mapping(conn);
symbols = xcb_key_symbols_alloc(conn);

View File

@ -6,7 +6,7 @@
# No dependencies except for perl ≥ v5.10
use strict;
use warnings;
use warnings qw(FATAL utf8);
use Data::Dumper;
use IPC::Open2;
use POSIX qw(locale_h);
@ -17,25 +17,35 @@ use Getopt::Long;
use Pod::Usage;
use v5.10;
use utf8;
use open ':encoding(utf8)';
use open ':encoding(UTF-8)';
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
# reads in a whole file
sub slurp {
open(my $fh, '<', shift) or die "$!";
my ($filename) = @_;
open(my $fh, '<', $filename) or die "$!";
local $/;
<$fh>;
my $result;
eval {
$result = <$fh>;
};
if ($@) {
warn "Could not read $filename: $@";
return undef;
} else {
return $result;
}
}
my $entry_type = 'both';
my @entry_types;
my $dmenu_cmd = 'dmenu -i';
my $result = GetOptions(
'dmenu=s' => \$dmenu_cmd,
'entry-type=s' => \$entry_type,
'entry-type=s' => \@entry_types,
'version' => sub {
say "dmenu-desktop 1.2 © 2012 Michael Stapelberg";
say "dmenu-desktop 1.4 © 2012 Michael Stapelberg";
exit 0;
},
'help' => sub {
@ -44,6 +54,11 @@ my $result = GetOptions(
die "Could not parse command line options" unless $result;
# Filter entry types and set default type(s) if none selected
my @valid_types = ('name', 'command', 'filename');
@entry_types = grep { $_ ~~ @valid_types } @entry_types;
@entry_types = ('name', 'command') unless @entry_types;
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃
# ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro ┃
@ -135,7 +150,9 @@ for my $file (values %desktops) {
# Extract all “Name” and “Exec” keys from the [Desktop Entry] group
# and store them in $apps{$base}.
my %names;
my @lines = split("\n", slurp($file));
my $content = slurp($file);
next unless defined($content);
my @lines = split("\n", $content);
for my $line (@lines) {
my $first = substr($line, 0, 1);
next if $line eq '' || $first eq '#';
@ -209,6 +226,13 @@ for my $app (keys %apps) {
next if (!exists($apps{$app}->{Type}) ||
$apps{$app}->{Type} ne 'Application');
# Skip broken files (Type=application, but no Exec key).
if (!exists($apps{$app}->{Exec}) ||
$apps{$app}->{Exec} eq '') {
warn 'File ' . $apps{$app}->{_Location} . ' is broken: it contains Type=Application, but no Exec key/value pair.';
next;
}
# Dont offer apps which have NoDisplay == true or Hidden == true.
# See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries
# for the difference between NoDisplay and Hidden.
@ -232,7 +256,7 @@ for my $app (keys %apps) {
}
}
if ($entry_type eq 'name' || $entry_type eq 'both') {
if ('name' ~~ @entry_types) {
if (exists($choices{$name})) {
# There are two .desktop files which contain the same “Name” value.
# Im not sure if that is allowed to happen, but we disambiguate the
@ -248,10 +272,25 @@ for my $app (keys %apps) {
$choices{$name} = $app;
}
if ($entry_type eq 'command' || $entry_type eq 'both') {
if ('command' ~~ @entry_types) {
my ($command) = split(' ', $apps{$app}->{Exec});
# Dont add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
next if lc(basename($command)) ~~ @keys;
$choices{basename($command)} = $app;
}
if ('filename' ~~ @entry_types) {
my $filename = basename($app, '.desktop');
# Dont add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
next if lc($filename) ~~ @keys;
$choices{$filename} = $app;
}
}
# %choices now looks like this:
@ -282,6 +321,8 @@ my $status = ($? >> 8);
exit $status unless $status == 0;
my $choice = <$dmenu_out>;
# dmenu ≥ 4.4 adds a newline after the choice
chomp($choice);
my $app;
# Exact match: the user chose “Avidemux (GTK+)”
if (exists($choices{$choice})) {
@ -341,6 +382,7 @@ $exec =~ s/%c/$name/g;
# XXX: Icons are not implemented. Is the complexity (looking up the path if
# only a name is given) actually worth it?
#$exec =~ s/%i/--icon $icon/g;
$exec =~ s/%i//g;
# location of .desktop file
$exec =~ s/%k/$location/g;
@ -392,7 +434,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
=head1 SYNOPSIS
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both]
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
=head1 DESCRIPTION
@ -441,17 +483,18 @@ version of dmenu.
=item B<--entry-type=type>
Display the (localized) "Name" (type = name) or the command (type = command) or
both (type = both) in dmenu.
Display the (localized) "Name" (type = name), the command (type = command) or
the (*.desktop) filename (type = filename) in dmenu. This option can be
specified multiple times.
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
command) and both (type = both).
command), and "libreoffice-writer" (type = filename).
=back
=head1 VERSION
Version 1.2
Version 1.4
=head1 AUTHOR

View File

@ -42,14 +42,19 @@ static int check_for_wrap(void) {
/* The log wrapped. Print the remaining content and reset walk to the top
* of the log. */
wrap_count = header->wrap_count;
write(STDOUT_FILENO, walk, ((logbuffer + header->offset_last_wrap) - walk));
const int len = (logbuffer + header->offset_last_wrap) - walk;
if (write(STDOUT_FILENO, walk, len) != len)
err(EXIT_FAILURE, "write()");
walk = logbuffer + sizeof(i3_shmlog_header);
return 1;
}
static void print_till_end(void) {
check_for_wrap();
int n = write(STDOUT_FILENO, walk, ((logbuffer + header->offset_next_write) - walk));
const int len = (logbuffer + header->offset_next_write) - walk;
const int n = write(STDOUT_FILENO, walk, len);
if (len != n)
err(EXIT_FAILURE, "write()");
if (n > 0) {
walk += n;
}
@ -121,7 +126,7 @@ int main(int argc, char *argv[]) {
struct stat statbuf;
/* NB: While we must never read, we need O_RDWR for the pthread condvar. */
/* NB: While we must never write, we need O_RDWR for the pthread condvar. */
int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
if (logbuffer_shm == -1)
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
@ -129,7 +134,7 @@ int main(int argc, char *argv[]) {
if (fstat(logbuffer_shm, &statbuf) != 0)
err(EXIT_FAILURE, "stat(%s)", shmname);
/* NB: While we must never read, we need O_RDWR for the pthread condvar. */
/* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED)
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-input/main.c: Utility which lets the user input commands and sends them
* to i3.
@ -76,6 +76,24 @@ void errorlog(char *fmt, ...) {
va_end(args);
}
/*
* Restores the X11 input focus to whereever it was before.
* This is necessary because i3-inputs window has override_redirect=1
* ( unmanaged by the window manager) and thus i3-input changes focus itself.
* This function is called on exit().
*
*/
static void restore_input_focus(void) {
xcb_generic_error_t *error;
xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
if (error != NULL) {
fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
return;
}
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
xcb_flush(conn);
}
/*
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
* rendering it (UCS-2) or sending it to i3 (UTF-8).
@ -187,6 +205,10 @@ static void finish_input() {
/* prefix the command if a prefix was specified on commandline */
printf("command = %s\n", full);
restore_input_focus();
xcb_aux_sync(conn);
ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
#if 0
@ -239,6 +261,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
return 1;
}
if (sym == XK_Escape) {
restore_input_focus();
exit(0);
}
@ -283,24 +306,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
return 1;
}
/*
* Restores the X11 input focus to whereever it was before.
* This is necessary because i3-inputs window has override_redirect=1
* ( unmanaged by the window manager) and thus i3-input changes focus itself.
* This function is called on exit().
*
*/
static void restore_input_focus(void) {
xcb_generic_error_t *error;
xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
if (error != NULL) {
fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
return;
}
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
xcb_flush(conn);
}
int main(int argc, char *argv[]) {
format = strdup("%s");
socket_path = getenv("I3SOCK");
@ -420,7 +425,6 @@ int main(int argc, char *argv[]) {
/* Set input focus (we have override_redirect=1, so the wm will not do
* this for us) */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
atexit(restore_input_focus);
/* Grab the keyboard to get all input */
xcb_flush(conn);
@ -440,6 +444,7 @@ int main(int argc, char *argv[]) {
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
restore_input_focus();
exit(-1);
}

View File

@ -5,7 +5,7 @@ CLEAN_TARGETS += clean-i3-msg
i3_msg_SOURCES := $(wildcard i3-msg/*.c)
i3_msg_HEADERS := $(wildcard i3-msg/*.h)
i3_msg_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS)
i3_msg_LIBS = $(XCB_LIBS)
i3_msg_LIBS = $(XCB_LIBS) $(YAJL_LIBS)
i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o)

View File

@ -28,6 +28,9 @@
#include <getopt.h>
#include <limits.h>
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -36,6 +39,99 @@
static char *socket_path;
/*
* Having verboselog() and errorlog() is necessary when using libi3.
*
*/
void verboselog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
}
void errorlog(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
static char *last_key = NULL;
typedef struct reply_t {
bool success;
char *error;
char *input;
char *errorposition;
} reply_t;
static reply_t last_reply;
static int reply_boolean_cb(void *params, int val) {
if (strcmp(last_key, "success") == 0)
last_reply.success = val;
return 1;
}
#if YAJL_MAJOR >= 2
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
#else
static int reply_string_cb(void *params, const unsigned char *val, unsigned int len) {
#endif
char *str = scalloc(len + 1);
strncpy(str, (const char*)val, len);
if (strcmp(last_key, "error") == 0)
last_reply.error = str;
else if (strcmp(last_key, "input") == 0)
last_reply.input = str;
else if (strcmp(last_key, "errorposition") == 0)
last_reply.errorposition = str;
else free(str);
return 1;
}
static int reply_start_map_cb(void *params) {
return 1;
}
static int reply_end_map_cb(void *params) {
if (!last_reply.success) {
fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
fprintf(stderr, "ERROR: %s\n", last_reply.error);
}
return 1;
}
#if YAJL_MAJOR >= 2
static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
#else
static int reply_map_key_cb(void *params, const unsigned char *keyVal, unsigned keyLen) {
#endif
free(last_key);
last_key = scalloc(keyLen + 1);
strncpy(last_key, (const char*)keyVal, keyLen);
return 1;
}
yajl_callbacks reply_callbacks = {
NULL,
&reply_boolean_cb,
NULL,
NULL,
NULL,
&reply_string_cb,
&reply_start_map_cb,
&reply_map_key_cb,
&reply_end_map_cb,
NULL,
NULL
};
int main(int argc, char *argv[]) {
socket_path = getenv("I3SOCK");
int o, option_index = 0;
@ -126,7 +222,7 @@ int main(int argc, char *argv[]) {
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1)
err(EXIT_FAILURE, "IPC: write()");
@ -135,13 +231,42 @@ int main(int argc, char *argv[]) {
return 0;
uint32_t reply_length;
uint32_t reply_type;
uint8_t *reply;
int ret;
if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
if (ret == -1)
err(EXIT_FAILURE, "IPC: read()");
exit(1);
}
if (reply_type != message_type)
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
/* For the reply of commands, have a look if that command was successful.
* If not, nicely format the error message. */
if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) {
yajl_handle handle;
#if YAJL_MAJOR < 2
yajl_parser_config parse_conf = { 0, 0 };
handle = yajl_alloc(&reply_callbacks, &parse_conf, NULL, NULL);
#else
handle = yajl_alloc(&reply_callbacks, NULL, NULL);
#endif
yajl_status state = yajl_parse(handle, (const unsigned char*)reply, reply_length);
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
#if YAJL_MAJOR < 2
case yajl_status_insufficient_data:
#endif
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
}
/* NB: We still fall-through and print the reply, because even if one
* command failed, that doesnt mean that all commands failed. */
}
printf("%.*s\n", reply_length, reply);
free(reply);

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-nagbar is a utility which displays a nag message, for example in the case
* when the user has an error in his configuration file.
@ -10,6 +10,7 @@
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdbool.h>
@ -20,6 +21,7 @@
#include <stdint.h>
#include <getopt.h>
#include <limits.h>
#include <fcntl.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -28,6 +30,8 @@
#include "libi3.h"
#include "i3-nagbar.h"
static char *argv0 = NULL;
typedef struct {
i3String *label;
char *action;
@ -135,7 +139,42 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
button_t *button = get_button_at(event->event_x, event->event_y);
if (!button)
return;
start_application(button->action);
/* We need to create a custom script containing our actual command
* since not every terminal emulator which is contained in
* i3-sensible-terminal supports -e with multiple arguments (and not
* all of them support -e with one quoted argument either).
*
* NB: The paths need to be unique, that is, dont assume users close
* their nagbars at any point in time (and they still need to work).
* */
char *script_path = get_process_filename("nagbar-cmd");
int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
warn("Could not create temporary script to store the nagbar command");
return;
}
FILE *script = fdopen(fd, "w");
if (script == NULL) {
warn("Could not fdopen() temporary script to store the nagbar command");
return;
}
fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action);
/* Also closes fd */
fclose(script);
char *terminal_cmd;
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0);
printf("argv0 = %s\n", argv0);
printf("terminal_cmd = %s\n", terminal_cmd);
setenv("_I3_NAGBAR_CMD", script_path, 1);
start_application(terminal_cmd);
unsetenv("_I3_NAGBAR_CMD");
free(terminal_cmd);
free(script_path);
/* TODO: unset flag, re-render */
}
@ -236,6 +275,29 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
}
int main(int argc, char *argv[]) {
/* The following lines are a horrible kludge. Because terminal emulators
* have different ways of interpreting the -e command line argument (some
* need -e "less /etc/fstab", others need -e less /etc/fstab), we need to
* write commands to a script and then just run that script. However, since
* on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec,
* we cannot directly execute the script either.
*
* Therefore, we run i3-nagbar instead and pass the path to the script in
* the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh
* with that path in order to run that script.
*
* From a security point of view, i3-nagbar is just an alias to /bin/sh in
* certain circumstances. This should not open any new security issues, I
* hope. */
char *cmd = NULL;
if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) {
unsetenv("_I3_NAGBAR_CMD");
execl("/bin/sh", "/bin/sh", cmd, NULL);
err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
}
argv0 = argv[0];
char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
int o, option_index = 0;
enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;

View File

@ -43,6 +43,10 @@ struct status_block {
blockalign_t align;
bool urgent;
bool no_separator;
/* The amount of pixels necessary to render a separater after the block. */
uint32_t sep_block_width;
/* The amount of pixels necessary to render this block. These variables are
* only temporarily used in refresh_statusline(). */

View File

@ -28,6 +28,7 @@
struct xcb_color_strings_t {
char *bar_fg;
char *bar_bg;
char *sep_fg;
char *active_ws_fg;
char *active_ws_bg;
char *active_ws_border;
@ -88,6 +89,15 @@ void get_atoms(void);
*/
void kick_tray_clients(i3_output *output);
/*
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
* they assume a light background.
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
*
*/
void init_tray_colors(void);
/*
* Destroy the bar of the specified output
*

View File

@ -6,6 +6,7 @@ ATOM_DO(MANAGER)
ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
ATOM_DO(_XEMBED_INFO)
ATOM_DO(_XEMBED)
#undef ATOM_DO

View File

@ -98,6 +98,10 @@ static int stdin_start_array(void *context) {
static int stdin_start_map(void *context) {
parser_ctx *ctx = context;
memset(&(ctx->block), '\0', sizeof(struct status_block));
/* Default width of the separator block. */
ctx->block.sep_block_width = 9;
return 1;
}
@ -117,6 +121,9 @@ static int stdin_boolean(void *context, int val) {
if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
ctx->block.urgent = val;
}
if (strcasecmp(ctx->last_map_key, "separator") == 0) {
ctx->block.no_separator = !val;
}
return 1;
}
@ -140,6 +147,10 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
} else {
ctx->block.align = ALIGN_CENTER;
}
} else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
i3String *text = i3string_from_utf8_with_length((const char *)val, len);
ctx->block.min_width = (uint32_t)predict_text_width(text);
i3string_free(text);
}
return 1;
}
@ -153,6 +164,9 @@ static int stdin_integer(void *context, long val) {
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
ctx->block.min_width = (uint32_t)val;
}
if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
ctx->block.sep_block_width = (uint32_t)val;
}
return 1;
}

View File

@ -161,6 +161,7 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
COLOR(statusline, bar_fg);
COLOR(background, bar_bg);
COLOR(separator, sep_fg);
COLOR(focused_workspace_border, focus_ws_border);
COLOR(focused_workspace_bg, focus_ws_bg);
COLOR(focused_workspace_text, focus_ws_fg);
@ -260,6 +261,7 @@ void free_colors(struct xcb_color_strings_t *colors) {
} while (0)
FREE_COLOR(bar_fg);
FREE_COLOR(bar_bg);
FREE_COLOR(sep_fg);
FREE_COLOR(active_ws_fg);
FREE_COLOR(active_ws_bg);
FREE_COLOR(active_ws_border);

View File

@ -84,6 +84,8 @@ void got_bar_config(char *reply) {
* workspaces. Everything else (creating the bars, showing the right workspace-
* buttons and more) is taken care of by the event-drivenness of the code */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
free_colors(&(config.colors));
parse_config_json(reply);
/* Now we can actually use 'config', so let's subscribe to the appropriate
@ -97,7 +99,6 @@ void got_bar_config(char *reply) {
/* Resolve color strings to colorpixels and save them, then free the strings. */
init_colors(&(config.colors));
free_colors(&(config.colors));
/* The name of this function is actually misleading. Even if no command is
* specified, this function initiates the watchers to listen on stdin and

View File

@ -49,6 +49,10 @@ int screen;
xcb_screen_t *root_screen;
xcb_window_t xcb_root;
/* selection window for tray support */
static xcb_window_t selwin = XCB_NONE;
static xcb_intern_atom_reply_t *tray_reply = NULL;
/* This is needed for integration with libi3 */
xcb_connection_t *conn;
@ -80,6 +84,7 @@ static mode binding;
struct xcb_colors_t {
uint32_t bar_fg;
uint32_t bar_bg;
uint32_t sep_fg;
uint32_t active_ws_fg;
uint32_t active_ws_bg;
uint32_t active_ws_border;
@ -145,7 +150,8 @@ void refresh_statusline(void) {
/* If this is not the last block, add some pixels for a separator. */
if (TAILQ_NEXT(block, blocks) != NULL)
block->width += 9;
block->width += block->sep_block_width;
statusline_width += block->width + block->x_offset + block->x_append;
}
@ -167,15 +173,19 @@ void refresh_statusline(void) {
uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 0, block->width);
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width);
x += block->width + block->x_offset + block->x_append;
if (TAILQ_NEXT(block, blocks) != NULL) {
if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) {
/* This is not the last block, draw a separator. */
set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
uint32_t sep_offset = block->sep_block_width/2 + block->sep_block_width % 2;
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
uint32_t values[] = { colors.sep_fg, colors.bar_bg };
xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
statusline_ctx, 2,
(xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
(xcb_point_t[]){ { x - sep_offset, 2 },
{ x - sep_offset, font.height - 2 } });
}
}
}
@ -255,6 +265,7 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
} while (0)
PARSE_COLOR(bar_fg, "#FFFFFF");
PARSE_COLOR(bar_bg, "#000000");
PARSE_COLOR(sep_fg, "#666666");
PARSE_COLOR(active_ws_fg, "#FFFFFF");
PARSE_COLOR(active_ws_bg, "#333333");
PARSE_COLOR(active_ws_border, "#333333");
@ -268,6 +279,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
PARSE_COLOR(focus_ws_bg, "#285577");
PARSE_COLOR(focus_ws_border, "#4c7899");
#undef PARSE_COLOR
init_tray_colors();
xcb_flush(xcb_connection);
}
/*
@ -991,6 +1005,31 @@ void init_xcb_late(char *fontname) {
}
}
/*
* Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
* selection.
*
*/
static void send_tray_clientmessage(void) {
uint8_t buffer[32] = { 0 };
xcb_client_message_event_t *ev = (xcb_client_message_event_t*)buffer;
ev->response_type = XCB_CLIENT_MESSAGE;
ev->window = xcb_root;
ev->type = atoms[MANAGER];
ev->format = 32;
ev->data.data32[0] = XCB_CURRENT_TIME;
ev->data.data32[1] = tray_reply->atom;
ev->data.data32[2] = selwin;
xcb_send_event(xcb_connection,
0,
xcb_root,
0xFFFFFF,
(char*)buffer);
}
/*
* Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
* for the X11 display we are running on, then acquiring the selection for this
@ -1003,11 +1042,11 @@ void init_tray(void) {
char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
xcb_intern_atom_cookie_t tray_cookie;
xcb_intern_atom_reply_t *tray_reply;
if (tray_reply == NULL)
tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
/* tray support: we need a window to own the selection */
xcb_window_t selwin = xcb_generate_id(xcb_connection);
selwin = xcb_generate_id(xcb_connection);
uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
uint32_t selval[] = { 1 };
xcb_create_window(xcb_connection,
@ -1016,7 +1055,7 @@ void init_tray(void) {
xcb_root,
-1, -1,
1, 1,
1,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
root_screen->root_visual,
selmask,
@ -1033,10 +1072,14 @@ void init_tray(void) {
1,
&orientation);
init_tray_colors();
if (tray_reply == NULL) {
if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
ELOG("Could not get atom %s\n", atomname);
exit(EXIT_FAILURE);
}
}
xcb_set_selection_owner(xcb_connection,
selwin,
@ -1062,23 +1105,48 @@ void init_tray(void) {
return;
}
/* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
void *event = scalloc(32);
xcb_client_message_event_t *ev = event;
ev->response_type = XCB_CLIENT_MESSAGE;
ev->window = xcb_root;
ev->type = atoms[MANAGER];
ev->format = 32;
ev->data.data32[0] = XCB_CURRENT_TIME;
ev->data.data32[1] = tray_reply->atom;
ev->data.data32[2] = selwin;
xcb_send_event(xcb_connection,
0,
xcb_root,
0xFFFFFF,
(char*)ev);
free(event);
free(tray_reply);
send_tray_clientmessage();
}
/*
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
* they assume a light background.
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
*
*/
void init_tray_colors(void) {
/* Convert colors.bar_fg (#rrggbb) to 16-bit RGB */
const char *bar_fg = (config.colors.bar_fg ? config.colors.bar_fg : "#FFFFFF");
DLOG("Setting bar_fg = %s as _NET_SYSTEM_TRAY_COLORS\n", bar_fg);
char strgroups[3][3] = {{bar_fg[1], bar_fg[2], '\0'},
{bar_fg[3], bar_fg[4], '\0'},
{bar_fg[5], bar_fg[6], '\0'}};
const uint8_t r = strtol(strgroups[0], NULL, 16);
const uint8_t g = strtol(strgroups[1], NULL, 16);
const uint8_t b = strtol(strgroups[2], NULL, 16);
const uint16_t r16 = ((uint16_t)r << 8) | r;
const uint16_t g16 = ((uint16_t)g << 8) | g;
const uint16_t b16 = ((uint16_t)b << 8) | b;
const uint32_t tray_colors[12] = {
r16, g16, b16, /* foreground color */
r16, g16, b16, /* error color */
r16, g16, b16, /* warning color */
r16, g16, b16, /* success color */
};
xcb_change_property(xcb_connection,
XCB_PROP_MODE_REPLACE,
selwin,
atoms[_NET_SYSTEM_TRAY_COLORS],
XCB_ATOM_CARDINAL,
32,
12,
tray_colors);
}
/*
@ -1138,6 +1206,9 @@ void get_atoms(void) {
*
*/
void kick_tray_clients(i3_output *output) {
if (TAILQ_EMPTY(output->trayclients))
return;
trayclient *trayclient;
while (!TAILQ_EMPTY(output->trayclients)) {
trayclient = TAILQ_FIRST(output->trayclients);
@ -1153,6 +1224,20 @@ void kick_tray_clients(i3_output *output) {
* event afterwards, but better safe than sorry. */
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
}
/* Fake a DestroyNotify so that Qt re-adds tray icons.
* We cannot actually destroy the window because then Qt will not restore
* its event mask on the new window. */
uint8_t buffer[32] = { 0 };
xcb_destroy_notify_event_t *event = (xcb_destroy_notify_event_t*)buffer;
event->response_type = XCB_DESTROY_NOTIFY;
event->event = selwin;
event->window = selwin;
xcb_send_event(conn, false, selwin, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)event);
send_tray_clientmessage();
}
/*
@ -1261,7 +1346,7 @@ void reconfig_windows(void) {
xcb_root,
walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
walk->rect.w, font.height + 6,
1,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
root_screen->root_visual,
mask,
@ -1398,7 +1483,7 @@ void reconfig_windows(void) {
values[3] = font.height + 6;
values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Destroying buffer for output %s", walk->name);
DLOG("Destroying buffer for output %s\n", walk->name);
xcb_free_pixmap(xcb_connection, walk->buffer);
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
@ -1407,7 +1492,7 @@ void reconfig_windows(void) {
mask,
values);
DLOG("Recreating buffer for output %s", walk->name);
DLOG("Recreating buffer for output %s\n", walk->name);
xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
root_screen->root_depth,
walk->buffer,
@ -1431,7 +1516,7 @@ void reconfig_windows(void) {
*/
void draw_bars(bool unhide) {
DLOG("Drawing Bars...\n");
int i = 0;
int i = 1;
refresh_statusline();
@ -1530,7 +1615,7 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
mask,
vals_border);
xcb_rectangle_t rect_border = { i, 0, ws_walk->name_width + 10, font.height + 4 };
xcb_rectangle_t rect_border = { i, 1, ws_walk->name_width + 10, font.height + 4 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
@ -1541,14 +1626,14 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
mask,
vals);
xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 2 };
xcb_rectangle_t rect = { i + 1, 2, ws_walk->name_width + 8, font.height + 2 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
1,
&rect);
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, ws_walk->name_width);
i += 10 + ws_walk->name_width + 1;
}
@ -1564,7 +1649,7 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
mask,
vals_border);
xcb_rectangle_t rect_border = { i, 0, binding.width + 10, font.height + 4 };
xcb_rectangle_t rect_border = { i, 1, binding.width + 10, font.height + 4 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
@ -1576,7 +1661,7 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
mask,
vals);
xcb_rectangle_t rect = { i + 1, 1, binding.width + 8, font.height + 2 };
xcb_rectangle_t rect = { i + 1, 2, binding.width + 8, font.height + 2 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
@ -1584,7 +1669,7 @@ void draw_bars(bool unhide) {
&rect);
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, binding.width);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
}
i = 0;

View File

@ -2,6 +2,7 @@ xmacro(_NET_SUPPORTED)
xmacro(_NET_SUPPORTING_WM_CHECK)
xmacro(_NET_WM_NAME)
xmacro(_NET_WM_STATE_FULLSCREEN)
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
xmacro(_NET_WM_STATE)
xmacro(_NET_WM_WINDOW_TYPE)
xmacro(_NET_WM_WINDOW_TYPE_DOCK)

View File

@ -324,6 +324,12 @@ bool con_has_urgent_child(Con *con);
*/
void con_update_parents_urgency(Con *con);
/**
* Set urgency flag to the container, all the parent containers and the workspace.
*
*/
void con_set_urgency(Con *con, bool urgent);
/**
* Create a string representing the subtree under con.
*

View File

@ -6,8 +6,8 @@
*
* include/config.h: Contains all structs/variables for the configurable
* part of i3 as well as functions handling the configuration file (calling
* the parser (src/cfgparse.y) with the correct path, switching key bindings
* mode).
* the parser (src/config_parse.c) with the correct path, switching key
* bindings mode).
*
*/
#ifndef I3_CONFIG_H
@ -24,8 +24,6 @@ extern char *current_configpath;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
/* defined in src/cfgparse.y */
extern bool force_old_config_parser;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
@ -269,6 +267,7 @@ struct Barconfig {
struct bar_colors {
char *background;
char *statusline;
char *separator;
char *focused_workspace_border;
char *focused_workspace_bg;
@ -342,7 +341,4 @@ Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode
*/
void kill_configerror_nagbar(bool wait_for_it);
/* prototype for src/cfgparse.y */
void parse_file(const char *f);
#endif

View File

@ -12,6 +12,8 @@
#include <yajl/yajl_gen.h>
extern pid_t config_error_nagbar_pid;
/*
* The result of a parse_config call. Currently unused, but the JSON output
* will be useful in the future when we implement a config parsing IPC command.
@ -29,4 +31,11 @@ struct ConfigResult {
struct ConfigResult *parse_config(const char *input, struct context *context);
/**
* Parses the given file by first replacing the variables, then calling
* parse_config and possibly launching i3-nagbar.
*
*/
void parse_file(const char *f);
#endif

View File

@ -196,7 +196,8 @@ struct regex {
/**
* Holds a keybinding, consisting of a keycode combined with modifiers and the
* command which is executed as soon as the key is pressed (see src/cfgparse.y)
* command which is executed as soon as the key is pressed (see
* src/config_parser.c)
*
*/
struct Binding {
@ -569,8 +570,14 @@ struct Con {
void(*on_remove_child)(Con *);
enum {
/* Not a scratchpad window. */
SCRATCHPAD_NONE = 0,
/* Just moved to scratchpad, not resized by the user yet.
* Window will be auto-centered and sized appropriately. */
SCRATCHPAD_FRESH = 1,
/* The user changed position/size of the scratchpad window. */
SCRATCHPAD_CHANGED = 2
} scratchpad_state;

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* This public header defines the different constants and message types to use
* for the IPC interface to i3 (see docs/ipc for more information).
@ -11,6 +11,15 @@
#ifndef I3_I3_IPC_H
#define I3_I3_IPC_H
#include <stdint.h>
typedef struct i3_ipc_header {
/* 6 = strlen(I3_IPC_MAGIC) */
char magic[6];
uint32_t size;
uint32_t type;
} __attribute__ ((packed)) i3_ipc_header_t;
/*
* Messages from clients to i3
*
@ -87,4 +96,7 @@
/* The output event will be triggered upon mode changes */
#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2)
/* The window event will be triggered upon window changes */
#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3)
#endif

View File

@ -10,6 +10,8 @@
#ifndef I3_KEY_PRESS_H
#define I3_KEY_PRESS_H
extern pid_t command_error_nagbar_pid;
/**
* There was a key press. We compare this key code with our bindings table and pass
* the bound action to parse_command().

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* libi3: contains functions which are used by i3 *and* accompanying tools such
* as i3-msg, i3-config-wizard,
@ -47,6 +47,9 @@ struct Font {
/** The height of the font, built from font_ascent + font_descent */
int height;
/** The pattern/name used to load the font. */
char *pattern;
union {
struct {
/** The xcb-id for the font */
@ -202,8 +205,8 @@ int ipc_connect(const char *socket_path);
* Returns 0 on success.
*
*/
int ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, const uint8_t *payload);
int ipc_send_message(int sockfd, const uint32_t message_size,
const uint32_t message_type, const uint8_t *payload);
/**
* Reads a message from the given socket file descriptor and stores its length
@ -216,7 +219,7 @@ int ipc_send_message(int sockfd, uint32_t message_size,
* Returns 0 on success.
*
*/
int ipc_recv_message(int sockfd, uint32_t message_type,
int ipc_recv_message(int sockfd, uint32_t *message_type,
uint32_t *reply_length, uint8_t **reply);
/**
@ -355,4 +358,10 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
*/
bool is_debug_build() __attribute__((const));
/**
* Returns the name of a temporary file with the specified prefix.
*
*/
char *get_process_filename(const char *prefix);
#endif

View File

@ -87,20 +87,29 @@ Output *get_output_by_name(const char *name);
*/
Output *get_output_containing(int x, int y);
/**
* 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);
/**
* Gets the output which is the next one in the given direction.
*
* If close_far == CLOSEST_OUTPUT, then the output next to the current one will
* selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
* in the given direction will be selected.
*
* NULL will be returned when no active outputs are present in the direction
* specified (note that current counts as such an output).
*
*/
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
/**
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
*
* For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
* get_output_next_wrap(D_DOWN, x) will return the topmost output.
*
* This function always returns a output: if no active outputs can be found,
* current itself is returned.
*
*/
Output *get_output_next_wrap(direction_t direction, Output *current);
#endif

View File

@ -21,4 +21,9 @@
*/
void render_con(Con *con, bool render_fullscreen);
/*
* Returns the height for the decorations
*/
int render_deco_height(void);
#endif

View File

@ -105,13 +105,6 @@ char *resolve_tilde(const char *path);
*/
bool path_exists(const char *path);
/**
* Returns the name of a temporary file with the specified prefix.
*
*/
char *get_process_filename(const char *prefix);
/**
* Restart i3 in-place
* appends -a to argument list to disable autostart
@ -130,4 +123,23 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
#endif
/**
* Starts an i3-nagbar instance with the given parameters. Takes care of
* handling SIGCHLD and killing i3-nagbar when i3 exits.
*
* The resulting PID will be stored in *nagbar_pid and can be used with
* kill_nagbar() to kill the bar later on.
*
*/
void start_nagbar(pid_t *nagbar_pid, char *argv[]);
/**
* Kills the i3-nagbar process, if *nagbar_pid != -1.
*
* If wait_for_it is set (restarting i3), this function will waitpid(),
* otherwise, ev is assumed to handle it (reloading).
*
*/
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
#endif

View File

@ -45,6 +45,15 @@
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \
XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */
#define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \
XCB_EVENT_MASK_BUTTON_PRESS | \
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video \
projector), the root window gets a \
ConfigureNotify */ \
XCB_EVENT_MASK_POINTER_MOTION | \
XCB_EVENT_MASK_PROPERTY_CHANGE | \
XCB_EVENT_MASK_ENTER_WINDOW)
#define xmacro(atom) xcb_atom_t A_ ## atom;
#include "atoms.xmacro"
#undef xmacro

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include <assert.h>
@ -143,14 +143,18 @@ i3Font load_font(const char *pattern, const bool fallback) {
#if PANGO_SUPPORT
/* Try to load a pango font if specified */
if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {
pattern += strlen("pango:");
if (load_pango_font(&font, pattern))
const char *font_pattern = pattern + strlen("pango:");
if (load_pango_font(&font, font_pattern)) {
font.pattern = sstrdup(pattern);
return font;
}
} else if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
pattern += strlen("xft:");
if (load_pango_font(&font, pattern))
const char *font_pattern = pattern + strlen("xft:");
if (load_pango_font(&font, font_pattern)) {
font.pattern = sstrdup(pattern);
return font;
}
}
#endif
/* Send all our requests first */
@ -189,6 +193,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
}
}
font.pattern = sstrdup(pattern);
LOG("Using X font %s\n", pattern);
/* Get information (height/name) for this font */
@ -222,6 +227,7 @@ void set_font(i3Font *font) {
*
*/
void free_font(void) {
free(savedFont->pattern);
switch (savedFont->type) {
case FONT_TYPE_NONE:
/* Nothing to do */

View File

@ -0,0 +1,58 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <err.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
#include "libi3.h"
/*
* Returns the name of a temporary file with the specified prefix.
*
*/
char *get_process_filename(const char *prefix) {
/* dir stores the directory path for this and all subsequent calls so that
* we only create a temporary directory once per i3 instance. */
static char *dir = NULL;
if (dir == NULL) {
/* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
if ((dir = getenv("XDG_RUNTIME_DIR"))) {
char *tmp;
sasprintf(&tmp, "%s/i3", dir);
dir = tmp;
struct stat buf;
if (stat(dir, &buf) != 0) {
if (mkdir(dir, 0700) == -1) {
perror("mkdir()");
return NULL;
}
}
} else {
/* If not, we create a (secure) temp directory using the template
* /tmp/i3-<user>.XXXXXX */
struct passwd *pw = getpwuid(getuid());
const char *username = pw ? pw->pw_name : "unknown";
sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
/* mkdtemp modifies dir */
if (mkdtemp(dir) == NULL) {
perror("mkdtemp()");
return NULL;
}
}
}
char *filename;
sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
return filename;
}

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include <string.h>
@ -10,6 +10,7 @@
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <i3/ipc.h>
@ -20,57 +21,56 @@
* (reply_length) as well as a pointer to its contents (reply).
*
* Returns -1 when read() fails, errno will remain.
* Returns -2 when the IPC protocol is violated (invalid magic, unexpected
* Returns -2 on EOF.
* Returns -3 when the IPC protocol is violated (invalid magic, unexpected
* message type, EOF instead of a message). Additionally, the error will be
* printed to stderr.
* Returns 0 on success.
*
*/
int ipc_recv_message(int sockfd, uint32_t message_type,
int ipc_recv_message(int sockfd, uint32_t *message_type,
uint32_t *reply_length, uint8_t **reply) {
/* Read the message header first */
uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
const uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
char msg[to_read];
char *walk = msg;
uint32_t read_bytes = 0;
while (read_bytes < to_read) {
int n = read(sockfd, msg + read_bytes, to_read);
int n = read(sockfd, msg + read_bytes, to_read - read_bytes);
if (n == -1)
return -1;
if (n == 0) {
fprintf(stderr, "IPC: received EOF instead of reply\n");
ELOG("IPC: received EOF instead of reply\n");
return -2;
}
read_bytes += n;
to_read -= n;
}
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
fprintf(stderr, "IPC: invalid magic in reply\n");
return -2;
ELOG("IPC: invalid magic in reply\n");
return -3;
}
walk += strlen(I3_IPC_MAGIC);
*reply_length = *((uint32_t*)walk);
walk += sizeof(uint32_t);
if (*((uint32_t*)walk) != message_type) {
fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type);
return -2;
}
if (message_type != NULL)
*message_type = *((uint32_t*)walk);
*reply = smalloc(*reply_length);
to_read = *reply_length;
read_bytes = 0;
while (read_bytes < to_read) {
int n = read(sockfd, *reply + read_bytes, to_read);
if (n == -1)
int n;
while (read_bytes < *reply_length) {
if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
return -1;
}
read_bytes += n;
to_read -= n;
}
return 0;

View File

@ -2,7 +2,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include <string.h>
@ -24,24 +24,35 @@
* Returns 0 on success.
*
*/
int ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, const uint8_t *payload) {
int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
strncpy(walk, I3_IPC_MAGIC, buffer_size - 1);
walk += strlen(I3_IPC_MAGIC);
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 ipc_send_message(int sockfd, const uint32_t message_size,
const uint32_t message_type, const uint8_t *payload) {
const i3_ipc_header_t header = {
/* We dont use I3_IPC_MAGIC because its a 0-terminated C string. */
.magic = { 'i', '3', '-', 'i', 'p', 'c' },
.size = message_size,
.type = message_type
};
int sent_bytes = 0;
while (sent_bytes < buffer_size) {
int n = write(sockfd, msg + sent_bytes, buffer_size - sent_bytes);
if (n == -1) {
int n = 0;
/* This first loop is basically unnecessary. No operating system has
* buffers which cannot fit 14 bytes into them, so the write() will only be
* called once. */
while (sent_bytes < sizeof(i3_ipc_header_t)) {
if ((n = write(sockfd, ((void*)&header) + sent_bytes, sizeof(i3_ipc_header_t) - sent_bytes)) == -1) {
if (errno == EAGAIN)
continue;
return -1;
}
sent_bytes += n;
}
sent_bytes = 0;
while (sent_bytes < message_size) {
if ((n = write(sockfd, payload + sent_bytes, message_size - sent_bytes)) == -1) {
if (errno == EAGAIN)
continue;
return -1;

View File

@ -7,7 +7,7 @@ template::[header-declarations]
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
<refmiscinfo class="version">4.4</refmiscinfo>
<refmiscinfo class="version">4.5</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>

View File

@ -229,7 +229,7 @@ state RESTART_STATE:
# popup_during_fullscreen
state POPUP_DURING_FULLSCREEN:
value = 'ignore', 'leave_fullscreen'
value = 'ignore', 'leave_fullscreen', 'smart'
-> call cfg_popup_during_fullscreen($value)
# client.background <hexcolor>
@ -272,7 +272,7 @@ state FONT:
state BINDING:
release = '--release'
->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
->
'+'
->
@ -317,7 +317,7 @@ state MODE_IGNORE_LINE:
state MODE_BINDING:
release = '--release'
->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
->
'+'
->
@ -419,7 +419,7 @@ state BAR_COLORS:
end ->
'#' -> BAR_COLORS_IGNORE_LINE
'set' -> BAR_COLORS_IGNORE_LINE
colorclass = 'background', 'statusline'
colorclass = 'background', 'statusline', 'separator'
-> BAR_COLORS_SINGLE
colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
-> BAR_COLORS_BORDER

View File

@ -1,301 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
*/
%option nounput
%option noinput
%option noyy_top_state
%option stack
%{
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include "log.h"
#include "data.h"
#include "config.h"
#include "util.h"
#include "libi3.h"
#include "cfgparse.tab.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; \
}
/* macro to first eat whitespace, then expect a string */
#define WS_STRING do { \
yy_push_state(WANT_STRING); \
yy_push_state(EAT_WHITESPACE); \
} while (0)
#define BAR_TRIPLE_COLOR do { \
yy_push_state(BAR_COLOR); \
yy_push_state(BAR_COLOR); \
yy_push_state(BAR_COLOR); \
} while (0)
%}
EOL (\r?\n)
%s WANT_STRING
%s WANT_QSTRING
%s BINDSYM_COND
%s ASSIGN_COND
%s ASSIGN_TARGET_COND
%s COLOR_COND
%s OUTPUT_COND
%s FOR_WINDOW_COND
%s EAT_WHITESPACE
%s BORDER_WIDTH
%x BUFFER_LINE
%x BAR
%x BAR_MODE
%x BAR_MODIFIER
%x BAR_POSITION
%x BAR_COLORS
%x BAR_COLOR
%x EXEC
%x OPTRELEASE
%%
{
/* 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 = sstrdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
yycolumn = 1;
}
/* This part of the lexer handles the bar {} blocks */
<BAR,BAR_MODE,BAR_MODIFIER,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
<BAR>"{" { return '{'; }
<BAR>"}" { yy_pop_state(); return '}'; }
<BAR>^[ \t]*#[^\n]* { return TOKCOMMENT; }
<BAR>output { WS_STRING; return TOK_BAR_OUTPUT; }
<BAR>tray_output { WS_STRING; return TOK_BAR_TRAY_OUTPUT; }
<BAR>socket_path { WS_STRING; return TOK_BAR_SOCKET_PATH; }
<BAR>mode { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
<BAR_MODE>hide { yy_pop_state(); return TOK_BAR_HIDE; }
<BAR_MODE>dock { yy_pop_state(); return TOK_BAR_DOCK; }
<BAR>modifier { yy_push_state(BAR_MODIFIER); return TOK_BAR_MODIFIER; }
<BAR_MODIFIER>control { yy_pop_state(); return TOK_BAR_CONTROL; }
<BAR_MODIFIER>ctrl { yy_pop_state(); return TOK_BAR_CONTROL; }
<BAR_MODIFIER>shift { yy_pop_state(); return TOK_BAR_SHIFT; }
<BAR_MODIFIER>Mod1 { yy_pop_state(); return TOK_BAR_MOD1; }
<BAR_MODIFIER>Mod2 { yy_pop_state(); return TOK_BAR_MOD2; }
<BAR_MODIFIER>Mod3 { yy_pop_state(); return TOK_BAR_MOD3; }
<BAR_MODIFIER>Mod4 { yy_pop_state(); return TOK_BAR_MOD4; }
<BAR_MODIFIER>Mod5 { yy_pop_state(); return TOK_BAR_MOD5; }
<BAR>position { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
<BAR_POSITION>bottom { yy_pop_state(); return TOK_BAR_BOTTOM; }
<BAR_POSITION>top { yy_pop_state(); return TOK_BAR_TOP; }
<BAR>status_command { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
<BAR>i3bar_command { WS_STRING; return TOK_BAR_I3BAR_COMMAND; }
<BAR>font { WS_STRING; return TOK_BAR_FONT; }
<BAR>workspace_buttons { return TOK_BAR_WORKSPACE_BUTTONS; }
<BAR>verbose { return TOK_BAR_VERBOSE; }
<BAR>colors { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; }
<BAR_COLORS>"{" { return '{'; }
<BAR_COLORS>"}" { yy_pop_state(); return '}'; }
<BAR_COLORS>^[ \t]*#[^\n]* { return TOKCOMMENT; }
<BAR_COLORS>background { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; }
<BAR_COLORS>statusline { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; }
<BAR_COLORS>focused_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
<BAR_COLORS>active_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
<BAR_COLORS>inactive_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
<BAR_COLORS>urgent_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
<BAR_COLOR>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
<BAR_COLOR>{EOL} {
yy_pop_state();
FREE(context->line_copy);
context->line_number++;
yy_push_state(BUFFER_LINE);
}
<BAR,BAR_COLORS,BAR_MODE,BAR_MODIFIER,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
<ASSIGN_COND>"[" {
/* this is the case for the new assign syntax
* that uses criteria */
yy_pop_state();
yy_push_state(FOR_WINDOW_COND);
/* afterwards we will be in ASSIGN_TARGET_COND */
return '[';
}
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
<EAT_WHITESPACE>{EOL} { yy_pop_state(); }
<BINDSYM_COND>{EOL} { yy_pop_state(); }
<WANT_QSTRING>\"[^\"]+\" {
yy_pop_state();
/* strip quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return STR;
}
<WANT_STRING>[^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
<OUTPUT_COND>[a-zA-Z0-9\/_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
<COLOR_COND>{EOL} {
yy_pop_state();
FREE(context->line_copy);
context->line_number++;
yy_push_state(BUFFER_LINE);
}
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
<BORDER_WIDTH>[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
<OPTRELEASE>--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
<OPTRELEASE>. { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
[0-9-]+ { yylval.number = atoi(yytext); return NUMBER; }
bar { yy_push_state(BAR); return TOK_BAR; }
mode { return TOKMODE; }
bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
floating_maximum_size { return TOKFLOATING_MAXIMUM_SIZE; }
floating_minimum_size { return TOKFLOATING_MINIMUM_SIZE; }
floating_modifier { return TOKFLOATING_MODIFIER; }
workspace { return TOKWORKSPACE; }
output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
terminal { WS_STRING; return TOKTERMINAL; }
font { WS_STRING; return TOKFONT; }
assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
set[^\n]* { return TOKCOMMENT; }
ipc-socket { WS_STRING; return TOKIPCSOCKET; }
ipc_socket { WS_STRING; return TOKIPCSOCKET; }
restart_state { WS_STRING; return TOKRESTARTSTATE; }
default_orientation { return TOK_ORIENTATION; }
horizontal { return TOK_HORIZ; }
vertical { return TOK_VERT; }
auto { return TOK_AUTO; }
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
new_window { return TOKNEWWINDOW; }
new_float { return TOKNEWFLOAT; }
normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
none { return TOK_NONE; }
1pixel { return TOK_1PIXEL; }
pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; }
both { return TOK_BOTH; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; }
force_xinerama { return TOK_FORCE_XINERAMA; }
force-xinerama { return TOK_FORCE_XINERAMA; }
fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; }
force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; }
ms { return TOK_TIME_MS; }
workspace_bar { return TOKWORKSPACEBAR; }
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
ignore { return TOK_IGNORE; }
leave_fullscreen { return TOK_LEAVE_FULLSCREEN; }
for_window {
/* Example: for_window [class="urxvt"] border none
*
* First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
* Then, we require a whitespace (EAT_WHITESPACE)
* And the rest of the line is parsed as a string
*/
yy_push_state(WANT_STRING);
yy_push_state(EAT_WHITESPACE);
yy_push_state(FOR_WINDOW_COND);
return TOK_FOR_WINDOW;
}
default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
stacked { return TOK_STACKING; }
tabbed { /* yylval.number = MODE_TABBED; */return TOK_TABBED; }
stack-limit { return TOKSTACKLIMIT; }
cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; }
exec_always { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; }
client.background { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
client.focused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
client.focused_inactive { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
client.unfocused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
client.urgent { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
bar.focused { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
bar.unfocused { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
bar.urgent { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
Mod1 { yylval.number = BIND_MOD1; return MODIFIER; }
Mod2 { yylval.number = BIND_MOD2; return MODIFIER; }
Mod3 { yylval.number = BIND_MOD3; return MODIFIER; }
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; }
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
id { yy_push_state(WANT_QSTRING); return TOK_ID; }
con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; }
title { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
urgent { yy_push_state(WANT_QSTRING); return TOK_URGENT; }
<*>{EOL} {
FREE(context->line_copy);
context->line_number++;
yy_push_state(BUFFER_LINE);
}
<BINDSYM_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
<OUTPUT_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
[ \t]+ { /* ignore whitespace */ ; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
if (yy_start_stack_ptr > 0)
yy_pop_state();
/* yylval will be the string, but without quotes */
char *copy = sstrdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return QUOTEDSTRING;
}
<ASSIGN_COND>[^ \t\"\[]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
<BINDSYM_COND>[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
. { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%%

File diff suppressed because it is too large Load Diff

View File

@ -55,23 +55,15 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) {
static Output *get_output_from_string(Output *current_output, const char *output_str) {
Output *output;
if (strcasecmp(output_str, "left") == 0) {
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_RIGHT, current_output);
} else if (strcasecmp(output_str, "right") == 0) {
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_LEFT, current_output);
} else if (strcasecmp(output_str, "up") == 0) {
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_DOWN, current_output);
} else if (strcasecmp(output_str, "down") == 0) {
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
if (!output)
output = get_output_most(D_UP, current_output);
} else output = get_output_by_name(output_str);
if (strcasecmp(output_str, "left") == 0)
output = get_output_next_wrap(D_LEFT, current_output);
else if (strcasecmp(output_str, "right") == 0)
output = get_output_next_wrap(D_RIGHT, current_output);
else if (strcasecmp(output_str, "up") == 0)
output = get_output_next_wrap(D_UP, current_output);
else if (strcasecmp(output_str, "down") == 0)
output = get_output_next_wrap(D_DOWN, current_output);
else output = get_output_by_name(output_str);
return output;
}
@ -596,10 +588,14 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
return;
if (strcmp(direction, "up") == 0) {
floating_con->rect.y -= px;
floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
} else if (strcmp(direction, "left") == 0) {
floating_con->rect.x -= px;
floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
}
/* If this is a scratchpad window, don't auto center it from now on. */
if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
}
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
@ -1052,13 +1048,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
// TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
if (strcasecmp(name, "up") == 0)
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
output = get_output_next_wrap(D_UP, current_output);
else if (strcasecmp(name, "down") == 0)
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
output = get_output_next_wrap(D_DOWN, current_output);
else if (strcasecmp(name, "left") == 0)
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
output = get_output_next_wrap(D_LEFT, current_output);
else if (strcasecmp(name, "right") == 0)
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
output = get_output_next_wrap(D_RIGHT, current_output);
else
output = get_output_by_name(name);
@ -1410,6 +1406,7 @@ void cmd_focus(I3_CMD) {
return;
}
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
int count = 0;
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
@ -1426,6 +1423,16 @@ void cmd_focus(I3_CMD) {
return;
}
/* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) {
scratchpad_show(current->con);
count++;
/* While for the normal focus case we can change focus multiple
* times and only a single window ends up focused, we could show
* multiple scratchpad windows. So, rather break here. */
break;
}
/* If the container is not on the current workspace,
* workspace_show() will switch to a different workspace and (if
* enabled) trigger a mouse pointer warp to the currently focused
@ -1602,8 +1609,8 @@ void cmd_exit(I3_CMD) {
*/
void cmd_reload(I3_CMD) {
LOG("reloading\n");
kill_configerror_nagbar(false);
kill_commanderror_nagbar(false);
kill_nagbar(&config_error_nagbar_pid, false);
kill_nagbar(&command_error_nagbar_pid, false);
load_configuration(conn, NULL, true);
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */

View File

@ -762,14 +762,9 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
con_focus(old_focus);
}
/* 8: when moving to a visible workspace on a different output, we keep the
* con focused. Otherwise, we leave the focus on the current workspace as we
* dont want to focus invisible workspaces */
if (source_output != dest_output &&
workspace_is_visible(workspace) &&
!con_is_internal(workspace)) {
DLOG("Moved to a different output, focusing target\n");
} else {
/* 8: when moving to another workspace, we leave the focus on the current
* workspace. (see also #809) */
/* Descend focus stack in case focus_next is a workspace which can
* occur if we move to the same workspace. Also show current workspace
* to ensure it is focused. */
@ -779,7 +774,6 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
* Otherwise we would give focus to some window on different workspace. */
if (source_ws == current_ws)
con_focus(con_descend_focused(focus_next));
}
/* If anything within the container is associated with a startup sequence,
* delete it so child windows won't be created on the old workspace. */
@ -1181,7 +1175,7 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
con->current_border_width = border_width;
bsr = con_border_style_rect(con);
int deco_height =
(con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
(con->border_style == BS_NORMAL ? render_deco_height() : 0);
con->rect.x -= bsr.x;
con->rect.y -= bsr.y;
@ -1536,6 +1530,45 @@ void con_update_parents_urgency(Con *con) {
}
}
/*
* Set urgency flag to the container, all the parent containers and the workspace.
*
*/
void con_set_urgency(Con *con, bool urgent) {
if (focused == con) {
DLOG("Ignoring urgency flag for current client\n");
con->window->urgent.tv_sec = 0;
con->window->urgent.tv_usec = 0;
return;
}
if (con->urgency_timer == NULL) {
con->urgent = urgent;
} else
DLOG("Discarding urgency WM_HINT because timer is running\n");
//CLIENT_LOG(con);
if (con->window) {
if (con->urgent) {
gettimeofday(&con->window->urgent, NULL);
} else {
con->window->urgent.tv_sec = 0;
con->window->urgent.tv_usec = 0;
}
}
con_update_parents_urgency(con);
if (con->urgent == urgent)
LOG("Urgency flag changed to %d\n", con->urgent);
Con *ws;
/* Set the urgency flag on the workspace, if a workspace could be found
* (for dock clients, that is not the case). */
if ((ws = con_get_workspace(con)) != NULL)
workspace_update_urgent_flag(ws);
}
/*
* Create a string representing the subtree under con.
*
@ -1573,6 +1606,10 @@ char *con_get_tree_representation(Con *con) {
buf = sstrdup("T[");
else if (con->layout == L_STACKED)
buf = sstrdup("S[");
else {
ELOG("BUG: Code not updated to account for new layout type\n");
assert(false);
}
/* 2) append representation of children */
Con *child;

View File

@ -6,8 +6,8 @@
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
*
* config.c: Configuration file (calling the parser (src/cfgparse.y) with the
* correct path, switching key bindings mode).
* config.c: Configuration file (calling the parser (src/config_parser.c) with
* the correct path, switching key bindings mode).
*
*/
#include "all.h"

View File

@ -392,8 +392,13 @@ CFGFUN(restart_state, const char *path) {
}
CFGFUN(popup_during_fullscreen, const char *value) {
config.popup_during_fullscreen =
(strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN);
if (strcmp(value, "ignore") == 0) {
config.popup_during_fullscreen = PDF_IGNORE;
} else if (strcmp(value, "leave_fullscreen") == 0) {
config.popup_during_fullscreen = PDF_LEAVE_FULLSCREEN;
} else {
config.popup_during_fullscreen = PDF_SMART;
}
}
CFGFUN(color_single, const char *colorclass, const char *color) {
@ -526,7 +531,10 @@ CFGFUN(bar_tray_output, const char *output) {
CFGFUN(bar_color_single, const char *colorclass, const char *color) {
if (strcmp(colorclass, "background") == 0)
current_bar.colors.background = sstrdup(color);
else current_bar.colors.statusline = sstrdup(color);
else if (strcmp(colorclass, "separator") == 0)
current_bar.colors.separator = sstrdup(color);
else
current_bar.colors.statusline = sstrdup(color);
}
CFGFUN(bar_status_command, const char *command) {

View File

@ -4,7 +4,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_parser.c: hand-written parser to parse configuration directives.
*
@ -31,6 +31,10 @@
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "all.h"
@ -38,6 +42,11 @@
#define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
#ifndef TEST_PARSER
pid_t config_error_nagbar_pid = -1;
static struct context *context;
#endif
/*******************************************************************************
* The data structures used for parsing. Essentially the current state and a
* list of tokens for that state.
@ -649,4 +658,441 @@ int main(int argc, char *argv[]) {
context.filename = "<stdin>";
parse_config(argv[1], &context);
}
#else
/*
* Goes through each line of buf (separated by \n) and checks for statements /
* commands which only occur in i3 v4 configuration files. If it finds any, it
* returns version 4, otherwise it returns version 3.
*
*/
static int detect_version(char *buf) {
char *walk = buf;
char *line = buf;
while (*walk != '\0') {
if (*walk != '\n') {
walk++;
continue;
}
/* check for some v4-only statements */
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
return 4;
}
/* if this is a bind statement, we can check the command */
if (strncasecmp(line, "bind", strlen("bind")) == 0) {
char *bind = strchr(line, ' ');
if (bind == NULL)
goto next;
while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
bind++;
if (*bind == '\0')
goto next;
if ((bind = strchr(bind, ' ')) == NULL)
goto next;
while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
bind++;
if (*bind == '\0')
goto next;
if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
strncasecmp(bind, "floating", strlen("floating")) == 0 ||
strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
strncasecmp(bind, "bar", strlen("bar")) == 0) {
printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
return 4;
}
}
next:
/* advance to the next line */
walk++;
line = walk;
}
return 3;
}
/*
* Calls i3-migrate-config-to-v4 to migrate a configuration file (input
* buffer).
*
* Returns the converted config file or NULL if there was an error (for
* example the script could not be found in $PATH or the i3 executables
* directory).
*
*/
static char *migrate_config(char *input, off_t size) {
int writepipe[2];
int readpipe[2];
if (pipe(writepipe) != 0 ||
pipe(readpipe) != 0) {
warn("migrate_config: Could not create pipes");
return NULL;
}
pid_t pid = fork();
if (pid == -1) {
warn("Could not fork()");
return NULL;
}
/* child */
if (pid == 0) {
/* close writing end of writepipe, connect reading side to stdin */
close(writepipe[1]);
dup2(writepipe[0], 0);
/* close reading end of readpipe, connect writing side to stdout */
close(readpipe[0]);
dup2(readpipe[1], 1);
static char *argv[] = {
NULL, /* will be replaced by the executable path */
NULL
};
exec_i3_utility("i3-migrate-config-to-v4", argv);
}
/* parent */
/* close reading end of the writepipe (connected to the scripts stdin) */
close(writepipe[0]);
/* write the whole config file to the pipe, the script will read everything
* immediately */
int written = 0;
int ret;
while (written < size) {
if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
warn("Could not write to pipe");
return NULL;
}
written += ret;
}
close(writepipe[1]);
/* close writing end of the readpipe (connected to the scripts stdout) */
close(readpipe[1]);
/* read the scripts output */
int conv_size = 65535;
char *converted = malloc(conv_size);
int read_bytes = 0;
do {
if (read_bytes == conv_size) {
conv_size += 65535;
converted = realloc(converted, conv_size);
}
ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
if (ret == -1) {
warn("Cannot read from pipe");
FREE(converted);
return NULL;
}
read_bytes += ret;
} while (ret > 0);
/* get the returncode */
int status;
wait(&status);
if (!WIFEXITED(status)) {
fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
return NULL;
}
int returncode = WEXITSTATUS(status);
if (returncode != 0) {
fprintf(stderr, "Migration process exit code was != 0\n");
if (returncode == 2) {
fprintf(stderr, "could not start the migration script\n");
/* TODO: script was not found. tell the user to fix his system or create a v4 config */
} else if (returncode == 1) {
fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
fprintf(stderr, "# i3 config file (v4)\n");
/* TODO: nag the user with a message to include a hint for i3 in his config file */
}
return NULL;
}
return converted;
}
/*
* Checks for duplicate key bindings (the same keycode or keysym is configured
* more than once). If a duplicate binding is found, a message is printed to
* stderr and the has_errors variable is set to true, which will start
* i3-nagbar.
*
*/
static void check_for_duplicate_bindings(struct context *context) {
Binding *bind, *current;
TAILQ_FOREACH(current, bindings, bindings) {
TAILQ_FOREACH(bind, bindings, bindings) {
/* Abort when we reach the current keybinding, only check the
* bindings before */
if (bind == current)
break;
/* Check if one is using keysym while the other is using bindsym.
* If so, skip. */
/* XXX: It should be checked at a later place (when translating the
* keysym to keycodes) if there are any duplicates */
if ((bind->symbol == NULL && current->symbol != NULL) ||
(bind->symbol != NULL && current->symbol == NULL))
continue;
/* If bind is NULL, current has to be NULL, too (see above).
* If the keycodes differ, it can't be a duplicate. */
if (bind->symbol != NULL &&
strcasecmp(bind->symbol, current->symbol) != 0)
continue;
/* Check if the keycodes or modifiers are different. If so, they
* can't be duplicate */
if (bind->keycode != current->keycode ||
bind->mods != current->mods ||
bind->release != current->release)
continue;
context->has_errors = true;
if (current->keycode != 0) {
ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
current->mods, current->keycode, current->command);
} else {
ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
current->mods, current->symbol, current->command);
}
}
}
}
/*
* Parses the given file by first replacing the variables, then calling
* parse_config and possibly launching i3-nagbar.
*
*/
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
struct stat stbuf;
char *buf;
FILE *fstr;
char buffer[1026], key[512], value[512];
if ((fd = open(f, O_RDONLY)) == -1)
die("Could not open configuration file: %s\n", strerror(errno));
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
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));
read_bytes += ret;
}
if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
die("Could not lseek: %s\n", strerror(errno));
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
while (!feof(fstr)) {
if (fgets(buffer, 1024, fstr) == NULL) {
if (feof(fstr))
break;
die("Could not read configuration file\n");
}
/* 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;
if (strcasecmp(key, "set") == 0) {
if (value[0] != '$') {
ELOG("Malformed variable assignment, name has to start with $\n");
continue;
}
/* get key/value for this variable */
char *v_key = value, *v_value;
if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
ELOG("Malformed variable assignment, need a value\n");
continue;
}
if (!(v_value = strstr(value, " ")))
v_value = strstr(value, "\t");
*(v_value++) = '\0';
while (*v_value == '\t' || *v_value == ' ')
v_value++;
struct Variable *new = scalloc(sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
DLOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
}
fclose(fstr);
/* For every custom variable, see how often it occurs in the file and
* how much extra bytes it requires when replaced. */
struct Variable *current, *nearest;
int extra_bytes = 0;
/* We need to copy the buffer because we need to invalidate the
* variables (otherwise we will count them twice, which is bad when
* 'extra' is negative) */
char *bufcopy = sstrdup(buf);
SLIST_FOREACH(current, &variables, variables) {
int extra = (strlen(current->value) - strlen(current->key));
char *next;
for (next = bufcopy;
next < (bufcopy + stbuf.st_size) &&
(next = strcasestr(next, current->key)) != NULL;
next += strlen(current->key)) {
*next = '_';
extra_bytes += extra;
}
}
FREE(bufcopy);
/* Then, allocate a new buffer and copy the file over to the new one,
* but replace occurences of our variables */
char *walk = buf, *destwalk;
char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
destwalk = new;
while (walk < (buf + stbuf.st_size)) {
/* Find the next variable */
SLIST_FOREACH(current, &variables, variables)
current->next_match = strcasestr(walk, current->key);
nearest = NULL;
int distance = stbuf.st_size;
SLIST_FOREACH(current, &variables, variables) {
if (current->next_match == NULL)
continue;
if ((current->next_match - walk) < distance) {
distance = (current->next_match - walk);
nearest = current;
}
}
if (nearest == NULL) {
/* If there are no more variables, we just copy the rest */
strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
destwalk += (buf + stbuf.st_size) - walk;
*destwalk = '\0';
break;
} else {
/* Copy until the next variable, then copy its value */
strncpy(destwalk, walk, distance);
strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
walk += distance + strlen(nearest->key);
destwalk += distance + strlen(nearest->value);
}
}
/* analyze the string to find out whether this is an old config file (3.x)
* or a new config file (4.x). If its old, we run the converter script. */
int version = detect_version(buf);
if (version == 3) {
/* We need to convert this v3 configuration */
char *converted = migrate_config(new, stbuf.st_size);
if (converted != NULL) {
ELOG("\n");
ELOG("****************************************************************\n");
ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
ELOG("\n");
ELOG("Please convert your config file to v4. You can use this command:\n");
ELOG(" mv %s %s.O\n", f, f);
ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
ELOG("****************************************************************\n");
ELOG("\n");
free(new);
new = converted;
} else {
printf("\n");
printf("**********************************************************************\n");
printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
printf("was not correctly installed on your system?\n");
printf("**********************************************************************\n");
printf("\n");
}
}
context = scalloc(sizeof(struct context));
context->filename = f;
struct ConfigResult *config_output = parse_config(new, context);
yajl_gen_free(config_output->json_gen);
check_for_duplicate_bindings(context);
if (context->has_errors || context->has_warnings) {
ELOG("FYI: You are using i3 version " I3_VERSION "\n");
if (version == 3)
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
char *editaction,
*pageraction;
sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f);
sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
char *argv[] = {
NULL, /* will be replaced by the executable path */
"-f",
config.font.pattern,
"-t",
(context->has_errors ? "error" : "warning"),
"-m",
(context->has_errors ?
"You have an error in your i3 config file!" :
"Your config is outdated. Please fix the warnings to make sure everything works."),
"-b",
"edit config",
editaction,
(errorfilename ? "-b" : NULL),
(context->has_errors ? "show errors" : "show warnings"),
pageraction,
NULL
};
start_nagbar(&config_error_nagbar_pid, argv);
free(editaction);
free(pageraction);
}
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);
}
}
#endif

View File

@ -101,15 +101,18 @@ void display_running_version(void) {
err(EXIT_FAILURE, "IPC: write()");
uint32_t reply_length;
uint32_t reply_type;
uint8_t *reply;
int ret;
if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_GET_VERSION,
&reply_length, &reply)) != 0) {
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
if (ret == -1)
err(EXIT_FAILURE, "IPC: read()");
exit(EXIT_FAILURE);
}
if (reply_type != I3_IPC_MESSAGE_TYPE_GET_VERSION)
errx(EXIT_FAILURE, "Got reply type %d, but expected %d (GET_VERSION)", reply_type, I3_IPC_MESSAGE_TYPE_GET_VERSION);
#if YAJL_MAJOR >= 2
yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
#else

View File

@ -164,5 +164,5 @@ void ewmh_setup_hints(void) {
/* Im not entirely sure if we need to keep _NET_WM_NAME on root. */
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 16, supported_atoms);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms);
}

View File

@ -39,6 +39,35 @@ void floating_check_size(Con *floating_con) {
const int floating_sane_min_height = 50;
const int floating_sane_min_width = 75;
Rect floating_sane_max_dimensions;
Con *focused_con = con_descend_focused(floating_con);
/* obey size increments */
if (focused_con->height_increment || focused_con->width_increment) {
Rect border_rect = con_border_style_rect(focused_con);
/* We have to do the opposite calculations that render_con() do
* to get the exact size we want. */
border_rect.width = -border_rect.width;
border_rect.width += 2 * focused_con->border_width;
border_rect.height = -border_rect.height;
border_rect.height += 2 * focused_con->border_width;
if (con_border_style(focused_con) == BS_NORMAL)
border_rect.height += render_deco_height();
if (focused_con->height_increment &&
floating_con->rect.height >= focused_con->base_height + border_rect.height) {
floating_con->rect.height -= focused_con->base_height + border_rect.height;
floating_con->rect.height -= floating_con->rect.height % focused_con->height_increment;
floating_con->rect.height += focused_con->base_height + border_rect.height;
}
if (focused_con->width_increment &&
floating_con->rect.width >= focused_con->base_width + border_rect.width) {
floating_con->rect.width -= focused_con->base_width + border_rect.width;
floating_con->rect.width -= floating_con->rect.width % focused_con->width_increment;
floating_con->rect.width += focused_con->base_width + border_rect.width;
}
}
/* Unless user requests otherwise (-1), ensure width/height do not exceed
* configured maxima or, if unconfigured, limit to combined width of all
@ -165,7 +194,7 @@ void floating_enable(Con *con, bool automatic) {
free(name);
/* find the height for the decorations */
int deco_height = config.font.height + 5;
int deco_height = render_deco_height();
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
@ -251,7 +280,7 @@ void floating_enable(Con *con, bool automatic) {
/* 5: Subtract the deco_height in order to make the floating window appear
* at precisely the position it specified in its original geometry (which
* is what applications might remember). */
deco_height = (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
nc->rect.y -= deco_height;
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
@ -409,6 +438,11 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
/* Drag the window */
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
con->scratchpad_state = SCRATCHPAD_CHANGED;
tree_render();
}
@ -447,26 +481,27 @@ DRAGGING_CB(resize_window_callback) {
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 */
Rect minimum = con_minimum_size(con);
dest_width = max(dest_width, minimum.width);
dest_height = max(dest_height, minimum.height);
/* 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));
}
con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
/* Obey window size */
floating_check_size(con);
/* 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);
dest_x = old_rect->x + (old_rect->width - con->rect.width);
if (corner & BORDER_TOP)
dest_y = old_rect->y + (old_rect->height - dest_height);
dest_y = old_rect->y + (old_rect->height - con->rect.height);
con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
con->rect.x = dest_x;
con->rect.y = dest_y;
/* TODO: dont re-render the whole tree just because we change
* coordinates of a floating window */
@ -507,6 +542,10 @@ void floating_resize_window(Con *con, const bool proportional,
struct resize_window_callback_params params = { corner, proportional, event };
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
con->scratchpad_state = SCRATCHPAD_CHANGED;
}
/*
@ -630,6 +669,11 @@ void floating_reposition(Con *con, Rect newrect) {
con->rect = newrect;
floating_maybe_reassign_ws(con);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
con->scratchpad_state = SCRATCHPAD_CHANGED;
tree_render();
}

View File

@ -339,7 +339,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
/* find the height for the decorations */
int deco_height = config.font.height + 5;
int deco_height = con->deco_rect.height;
/* we actually need to apply the size/position changes to the *parent*
* container */
Rect bsr = con_border_style_rect(con);
@ -619,10 +619,10 @@ static void handle_client_message(xcb_client_message_event_t *event) {
LOG("ClientMessage for window 0x%08x\n", event->window);
if (event->type == A__NET_WM_STATE) {
if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
DLOG("atom in clientmessage is %d, fullscreen is %d\n",
event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
DLOG("not about fullscreen atom\n");
if (event->format != 32 ||
(event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN &&
event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION)) {
DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]);
return;
}
@ -632,6 +632,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
return;
}
if (event->data.data32[1] == A__NET_WM_STATE_FULLSCREEN) {
/* Check if the fullscreen state should be toggled */
if ((con->fullscreen_mode != CF_NONE &&
(event->data.data32[0] == _NET_WM_STATE_REMOVE ||
@ -642,6 +643,15 @@ static void handle_client_message(xcb_client_message_event_t *event) {
DLOG("toggling fullscreen\n");
con_toggle_fullscreen(con, CF_OUTPUT);
}
} else if (event->data.data32[1] == A__NET_WM_STATE_DEMANDS_ATTENTION) {
/* Check if the urgent flag must be set or not */
if (event->data.data32[0] == _NET_WM_STATE_ADD)
con_set_urgency(con, true);
else if (event->data.data32[0] == _NET_WM_STATE_REMOVE)
con_set_urgency(con, false);
else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
con_set_urgency(con, !con->urgent);
}
tree_render();
} else if (event->type == A__NET_ACTIVE_WINDOW) {
@ -833,44 +843,12 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
return false;
if (!con->urgent && focused == con) {
DLOG("Ignoring urgency flag for current client\n");
con->window->urgent.tv_sec = 0;
con->window->urgent.tv_usec = 0;
goto end;
}
/* Update the flag on the client directly */
bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
if (con->urgency_timer == NULL) {
con->urgent = hint_urgent;
} else
DLOG("Discarding urgency WM_HINT because timer is running\n");
//CLIENT_LOG(con);
if (con->window) {
if (con->urgent) {
gettimeofday(&con->window->urgent, NULL);
} else {
con->window->urgent.tv_sec = 0;
con->window->urgent.tv_usec = 0;
}
}
con_update_parents_urgency(con);
LOG("Urgency flag changed to %d\n", con->urgent);
Con *ws;
/* Set the urgency flag on the workspace, if a workspace could be found
* (for dock clients, that is not the case). */
if ((ws = con_get_workspace(con)) != NULL)
workspace_update_urgent_flag(ws);
con_set_urgency(con, hint_urgent);
tree_render();
end:
if (con->window)
window_update_hints(con->window, reply);
else free(reply);
@ -1094,7 +1072,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
/* Client message are sent to the root window. The only interesting
* client message for us is _NET_WM_STATE, we honour
* _NET_WM_STATE_FULLSCREEN */
* _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION */
case XCB_CLIENT_MESSAGE:
handle_client_message((xcb_client_message_event_t*)event);
break;

View File

@ -2,7 +2,6 @@ ALL_TARGETS += i3
INSTALL_TARGETS += install-i3
CLEAN_TARGETS += clean-i3
i3_SOURCES_GENERATED = src/cfgparse.tab.c src/cfgparse.yy.c
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
@ -37,14 +36,6 @@ src/%.o: src/%.c $(i3_HEADERS_DEP)
echo "[i3] CC $<"
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP)
echo "[i3] LEX $<"
$(FLEX) -i -o $@ ${canonical_path}/$<
src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
echo "[i3] YACC $<"
$(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$<
# This target compiles the command parser twice:
# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
# and once as an object file for i3.
@ -96,4 +87,4 @@ install-i3: i3
clean-i3:
echo "[i3] Clean"
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.*

View File

@ -677,6 +677,7 @@ IPC_HANDLER(get_bar_config) {
y(map_open);
YSTR_IF_SET(background);
YSTR_IF_SET(statusline);
YSTR_IF_SET(separator);
YSTR_IF_SET(focused_workspace_border);
YSTR_IF_SET(focused_workspace_bg);
YSTR_IF_SET(focused_workspace_text);
@ -802,18 +803,16 @@ handler_t handlers[8] = {
*
*/
static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
char buf[2048];
int n = read(w->fd, buf, sizeof(buf));
uint32_t message_type;
uint32_t message_length;
uint8_t *message;
/* On error or an empty message, we close the connection */
if (n <= 0) {
#if 0
/* FIXME: I get these when closing a client socket,
* therefore we just treat them as an error. Is this
* correct? */
if (errno == EAGAIN || errno == EWOULDBLOCK)
int ret = ipc_recv_message(w->fd, &message_type, &message_length, &message);
/* EOF or other error */
if (ret < 0) {
/* Was this a spurious read? See ev(3) */
if (ret == -1 && errno == EAGAIN)
return;
#endif
/* If not, there was some kind of error. We dont bother
* and close the connection */
@ -841,51 +840,11 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
return;
}
/* Terminate the message correctly */
buf[n] = '\0';
/* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) {
DLOG("IPC: message too short, ignoring\n");
return;
}
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
DLOG("IPC: message does not start with the IPC magic\n");
return;
}
uint8_t *message = (uint8_t*)buf;
while (n > 0) {
DLOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
/* The next 32 bit after the magic are the message size */
uint32_t message_size;
memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t));
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
if (message_size > n) {
DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
return;
}
/* The last 32 bits of the header are the message type */
uint32_t message_type;
memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t));
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
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;
h(w->fd, message, 0, message_length, message_type);
}
}

View File

@ -4,7 +4,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* key_press.c: key press handler
*
@ -19,169 +19,7 @@ static int current_nesting_level;
static bool parse_error_key;
static bool command_failed;
/* XXX: I dont want to touch too much of the nagbar code at once, but we
* should refactor this with src/cfgparse.y into a clean generic nagbar
* interface. It might come in handy in other situations within i3, too. */
static char *pager_script_path;
static pid_t nagbar_pid = -1;
/*
* Handler which will be called when we get a SIGCHLD for the nagbar, meaning
* it exited (or could not be started, depending on the exit code).
*
*/
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
ev_child_stop(EV_A_ watcher);
if (unlink(pager_script_path) != 0)
warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
if (!WIFEXITED(watcher->rstatus)) {
fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
return;
}
int exitcode = WEXITSTATUS(watcher->rstatus);
printf("i3-nagbar process exited with status %d\n", exitcode);
if (exitcode == 2) {
fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
}
nagbar_pid = -1;
}
/* We need ev >= 4 for the following code. Since it is not *that* important (it
* only makes sure that there are no i3-nagbar instances left behind) we still
* support old systems with libev 3. */
#if EV_VERSION_MAJOR >= 4
/*
* Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
* SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
*
*/
static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
if (nagbar_pid != -1) {
LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid);
kill(nagbar_pid, SIGKILL);
}
}
#endif
/*
* Writes the given command as a shell script to path.
* Returns true unless something went wrong.
*
*/
static bool write_nagbar_script(const char *path, const char *command) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
if (fd == -1) {
warn("Could not create temporary script to store the nagbar command");
return false;
}
write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
write(fd, command, strlen(command));
close(fd);
return true;
}
/*
* Starts an i3-nagbar process which alerts the user that his configuration
* file contains one or more errors. Also offers two buttons: One to launch an
* $EDITOR on the config file and another one to launch a $PAGER on the error
* logfile.
*
*/
static void start_commanderror_nagbar(void) {
if (nagbar_pid != -1) {
DLOG("i3-nagbar for command error already running, not starting again.\n");
return;
}
DLOG("Starting i3-nagbar due to command error\n");
/* We need to create a custom script containing our actual command
* since not every terminal emulator which is contained in
* i3-sensible-terminal supports -e with multiple arguments (and not
* all of them support -e with one quoted argument either).
*
* NB: The paths need to be unique, that is, dont assume users close
* their nagbars at any point in time (and they still need to work).
* */
pager_script_path = get_process_filename("nagbar-cfgerror-pager");
nagbar_pid = fork();
if (nagbar_pid == -1) {
warn("Could not fork()");
return;
}
/* child */
if (nagbar_pid == 0) {
char *pager_command;
sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
if (!write_nagbar_script(pager_script_path, pager_command))
return;
char *pageraction;
sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
char *argv[] = {
NULL, /* will be replaced by the executable path */
"-t",
"error",
"-m",
"The configured command for this shortcut could not be run successfully.",
"-b",
"show errors",
pageraction,
NULL
};
exec_i3_utility("i3-nagbar", argv);
}
/* parent */
/* install a child watcher */
ev_child *child = smalloc(sizeof(ev_child));
ev_child_init(child, &nagbar_exited, nagbar_pid, 0);
ev_child_start(main_loop, child);
/* We need ev >= 4 for the following code. Since it is not *that* important (it
* only makes sure that there are no i3-nagbar instances left behind) we still
* support old systems with libev 3. */
#if EV_VERSION_MAJOR >= 4
/* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
* still running) */
ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
ev_cleanup_init(cleanup, nagbar_cleanup);
ev_cleanup_start(main_loop, cleanup);
#endif
}
/*
* Kills the commanderror i3-nagbar process, if any.
*
* Called when reloading/restarting, since the user probably fixed his wrong
* keybindings.
*
* If wait_for_it is set (restarting), this function will waitpid(), otherwise,
* ev is assumed to handle it (reloading).
*
*/
void kill_commanderror_nagbar(bool wait_for_it) {
if (nagbar_pid == -1)
return;
if (kill(nagbar_pid, SIGTERM) == -1)
warn("kill(configerror_nagbar) failed");
if (!wait_for_it)
return;
/* When restarting, we dont enter the ev main loop anymore and after the
* exec(), our old pid is no longer watched. So, ev wont handle SIGCHLD
* for us and we would end up with a <defunct> process. Therefore we
* waitpid() here. */
waitpid(nagbar_pid, NULL, 0);
}
pid_t command_error_nagbar_pid = -1;
static int json_boolean(void *ctx, int boolval) {
DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level);
@ -302,8 +140,25 @@ void handle_key_press(xcb_key_press_event_t *event) {
if (state != yajl_status_ok) {
ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply);
} else {
if (command_failed)
start_commanderror_nagbar();
if (command_failed) {
char *pageraction;
sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
char *argv[] = {
NULL, /* will be replaced by the executable path */
"-f",
config.font.pattern,
"-t",
"error",
"-m",
"The configured command for this shortcut could not be run successfully.",
"-b",
"show errors",
pageraction,
NULL
};
start_nagbar(&command_error_nagbar_pid, argv);
free(pageraction);
}
}
yajl_free(handle);

View File

@ -374,8 +374,7 @@ int main(int argc, char *argv[]) {
fake_outputs = sstrdup(optarg);
break;
} else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) {
LOG("FORCING OLD CONFIG PARSER!\n");
force_old_config_parser = true;
ELOG("You are passing --force-old-config-parser-v4.4-only, but that flag was removed by now.\n");
break;
}
/* fall-through */
@ -461,14 +460,16 @@ int main(int argc, char *argv[]) {
err(EXIT_FAILURE, "IPC: write()");
uint32_t reply_length;
uint32_t reply_type;
uint8_t *reply;
int ret;
if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND,
&reply_length, &reply)) != 0) {
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
if (ret == -1)
err(EXIT_FAILURE, "IPC: read()");
return 1;
}
if (reply_type != I3_IPC_MESSAGE_TYPE_COMMAND)
errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_MESSAGE_TYPE_COMMAND);
printf("%.*s\n", reply_length, reply);
return 0;
}
@ -542,17 +543,8 @@ int main(int argc, char *argv[]) {
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
}
uint32_t mask = XCB_CW_EVENT_MASK;
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
projector), the root window gets a
ConfigureNotify */
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
check_error(conn, cookie, "Another window manager seems to be running");
xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL);
@ -603,11 +595,11 @@ int main(int argc, char *argv[]) {
int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n");
return 1;
xkb_supported = false;
}
/* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
if (xkb_supported && !XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
XkbMapNotifyMask | XkbStateNotifyMask,
XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n");

View File

@ -10,6 +10,9 @@
*
*/
#include "all.h"
#include "yajl_utils.h"
#include <yajl/yajl_gen.h>
/*
* Go through all existing windows (if the window manager is restarted) and manage them
@ -64,8 +67,41 @@ void restore_geometry(void) {
con->rect.x, con->rect.y);
}
/* Strictly speaking, this line doesnt really belong here, but since we
* are syncing, lets un-register as a window manager first */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
/* Make sure our changes reach the X server, we restart/exit now */
xcb_flush(conn);
xcb_aux_sync(conn);
}
/*
* The following function sends a new window event, which consists
* of fields "change" and "container", the latter containing a dump
* of the window's container.
*
*/
static void ipc_send_window_new_event(Con *con) {
setlocale(LC_NUMERIC, "C");
yajl_gen gen = ygenalloc();
y(map_open);
ystr("change");
ystr("new");
ystr("container");
dump_node(gen, con, false);
y(map_close);
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("window", I3_IPC_EVENT_WINDOW, (const char *)payload);
y(free);
setlocale(LC_NUMERIC, "");
}
/*
@ -349,10 +385,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
con_toggle_fullscreen(fs, CF_OUTPUT);
} else if (config.popup_during_fullscreen == PDF_SMART &&
fs != NULL &&
fs->window != NULL &&
fs->window->id == cwindow->transient_for) {
fs->window != NULL) {
i3Window *transient_win = cwindow;
while (transient_win != NULL &&
transient_win->transient_for != XCB_NONE) {
if (transient_win->transient_for == fs->window->id) {
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
con_focus(nc);
break;
}
Con *next_transient = con_by_window_id(transient_win->transient_for);
if (next_transient == NULL)
break;
transient_win = next_transient->window;
}
}
}
@ -424,6 +470,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
}
tree_render();
/* Send an event about window creation */
ipc_send_window_new_event(nc);
geom_out:
free(geom);
out:

View File

@ -4,7 +4,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* output.c: Output (monitor) related functions.
*
@ -22,6 +22,5 @@ Con *output_get_content(Con *output) {
if (child->type == CT_CON)
return child;
ELOG("output_get_content() called on non-output %p\n", output);
assert(false);
return NULL;
}

View File

@ -93,15 +93,30 @@ Output *get_output_containing(int x, int y) {
}
/*
* 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.
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
*
* This function always returns a output.
* For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
* get_output_next_wrap(D_DOWN, x) will return the topmost output.
*
* This function always returns a output: if no active outputs can be found,
* current itself is returned.
*
*/
Output *get_output_most(direction_t direction, Output *current) {
Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
Output *get_output_next_wrap(direction_t direction, Output *current) {
Output *best = get_output_next(direction, current, CLOSEST_OUTPUT);
/* If no output can be found, wrap */
if (!best) {
direction_t opposite;
if (direction == D_RIGHT)
opposite = D_LEFT;
else if (direction == D_LEFT)
opposite = D_RIGHT;
else if (direction == D_DOWN)
opposite = D_UP;
else
opposite = D_DOWN;
best = get_output_next(opposite, current, FARTHEST_OUTPUT);
}
if (!best)
best = current;
DLOG("current = %s, best = %s\n", current->name, best->name);
@ -111,6 +126,13 @@ Output *get_output_most(direction_t direction, Output *current) {
/*
* Gets the output which is the next one in the given direction.
*
* If close_far == CLOSEST_OUTPUT, then the output next to the current one will
* selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
* in the given direction will be selected.
*
* NULL will be returned when no active outputs are present in the direction
* specified (note that current counts as such an output).
*
*/
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
Rect *cur = &(current->rect),

View File

@ -16,6 +16,16 @@
* container (for debugging purposes) */
static bool show_debug_borders = false;
/*
* Returns the height for the decorations
*/
int render_deco_height(void) {
int deco_height = config.font.height + 4;
if (config.font.height & 0x01)
++deco_height;
return deco_height;
}
/*
* Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs
* get the height of their content and the remaining CT_CON gets the rest.
@ -44,12 +54,19 @@ static void render_l_output(Con *con) {
}
}
assert(content != NULL);
if (content == NULL) {
DLOG("Skipping this output because it is currently being destroyed.\n");
return;
}
/* We need to find out if there is a fullscreen con on the current workspace
* and take the short-cut to render it directly (the user does not want to
* see the dockareas in that case) */
Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
if (!ws) {
DLOG("Skipping this output because it is currently being destroyed.\n");
return;
}
Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
if (fullscreen) {
fullscreen->rect = con->rect;
@ -196,12 +213,11 @@ void render_con(Con *con, bool render_fullscreen) {
}
/* find the height for the decorations */
int deco_height = config.font.height + 4;
if (config.font.height & 0x01)
++deco_height;
int deco_height = render_deco_height();
/* precalculate the sizes to be able to correct rounding errors */
int sizes[children];
memset(sizes, 0, children*sizeof(int));
if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
assert(!TAILQ_EMPTY(&con->nodes_head));
Con *child;
@ -244,6 +260,10 @@ void render_con(Con *con, bool render_fullscreen) {
continue;
/* Get the active workspace of that output */
Con *content = output_get_content(output);
if (!content || TAILQ_EMPTY(&(content->focus_head))) {
DLOG("Skipping this output because it is currently being destroyed.\n");
continue;
}
Con *workspace = TAILQ_FIRST(&(content->focus_head));
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
Con *child;
@ -251,18 +271,28 @@ void render_con(Con *con, bool render_fullscreen) {
/* Dont render floating windows when there is a fullscreen window
* on that workspace. Necessary to make floating fullscreen work
* correctly (ticket #564). */
if (fullscreen != NULL) {
if (fullscreen != NULL && fullscreen->window != NULL) {
Con *floating_child = con_descend_focused(child);
Con *transient_con = floating_child;
bool is_transient_for = false;
/* Exception to the above rule: smart
* popup_during_fullscreen handling (popups belonging to
* the fullscreen app will be rendered). */
if (floating_child->window == NULL ||
fullscreen->window == NULL ||
floating_child->window->transient_for != fullscreen->window->id)
while (transient_con != NULL &&
transient_con->window != NULL &&
transient_con->window->transient_for != XCB_NONE) {
if (transient_con->window->transient_for == fullscreen->window->id) {
is_transient_for = true;
break;
}
transient_con = con_by_window_id(transient_con->window->transient_for);
}
if (!is_transient_for)
continue;
else {
DLOG("Rendering floating child even though in fullscreen mode: "
"floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n",
"floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
floating_child->window->transient_for, fullscreen->window->id);
}
}
@ -297,7 +327,8 @@ void render_con(Con *con, bool render_fullscreen) {
}
/* first we have the decoration, if this is a leaf node */
if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
if (con_is_leaf(child)) {
if (child->border_style == BS_NORMAL) {
/* TODO: make a function for relative coords? */
child->deco_rect.x = child->rect.x - con->rect.x;
child->deco_rect.y = child->rect.y - con->rect.y;
@ -307,6 +338,12 @@ void render_con(Con *con, bool render_fullscreen) {
child->deco_rect.width = child->rect.width;
child->deco_rect.height = deco_height;
} else {
child->deco_rect.x = 0;
child->deco_rect.y = 0;
child->deco_rect.width = 0;
child->deco_rect.height = 0;
}
}
}

View File

@ -4,7 +4,7 @@
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
*
* scratchpad.c: Moving windows to the scratchpad and making them visible again.
*
@ -53,7 +53,6 @@ void scratchpad_move(Con *con) {
/* 2: Send the window to the __i3_scratch workspace, mainting its
* coordinates and not warping the pointer. */
Con *focus_next = con_next_focused(con);
con_move_to_workspace(con, __i3_scratch, true, true);
/* 3: If this is the first time this window is used as a scratchpad, we set
@ -63,11 +62,6 @@ void scratchpad_move(Con *con) {
DLOG("This window was never used as a scratchpad before.\n");
con->scratchpad_state = SCRATCHPAD_FRESH;
}
/* 4: Fix focus. Normally, when moving a window to a different output, the
* destination output gets focused. In this case, we dont want that. */
if (con_get_workspace(focus_next) == con_get_workspace(focused))
con_focus(focus_next);
}
/*
@ -94,6 +88,41 @@ void scratchpad_show(Con *con) {
con_toggle_fullscreen(focused, CF_OUTPUT);
}
/* If this was 'scratchpad show' without criteria, we check if there is a
* unfocused scratchpad on the current workspace and focus it */
Con *walk_con;
Con *focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) {
if ((floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE &&
floating != con_inside_floating(focused)) {
DLOG("Found an unfocused scratchpad window on this workspace\n");
DLOG("Focusing it: %p\n", walk_con);
/* use con_descend_tiling_focused to get the last focused
* window inside this scratch container in order to
* keep the focus the same within this container */
con_focus(con_descend_tiling_focused(walk_con));
return;
}
}
/* If this was 'scratchpad show' without criteria, we check if there is a
* visible scratchpad window on another workspace. In this case we move it
* to the current workspace. */
focused_ws = con_get_workspace(focused);
TAILQ_FOREACH(walk_con, &all_cons, all_cons) {
Con *walk_ws = con_get_workspace(walk_con);
if (walk_ws &&
!con_is_internal(walk_ws) && focused_ws != walk_ws &&
(floating = con_inside_floating(walk_con)) &&
floating->scratchpad_state != SCRATCHPAD_NONE) {
DLOG("Found a visible scratchpad window on another workspace,\n");
DLOG("moving it to this workspace: con = %p\n", walk_con);
con_move_to_workspace(walk_con, focused_ws, true, false);
return;
}
}
/* If this was 'scratchpad show' without criteria, we check if the
* currently focused window is a scratchpad window and should be hidden
* again. */
@ -144,11 +173,11 @@ void scratchpad_show(Con *con) {
Con *output = con_get_output(con);
con->rect.width = output->rect.width * 0.5;
con->rect.height = output->rect.height * 0.75;
floating_check_size(con);
con->rect.x = output->rect.x +
((output->rect.width / 2.0) - (con->rect.width / 2.0));
con->rect.y = output->rect.y +
((output->rect.height / 2.0) - (con->rect.height / 2.0));
con->scratchpad_state = SCRATCHPAD_CHANGED;
}
/* Activate active workspace if window is from another workspace to ensure

View File

@ -200,6 +200,13 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
was_mapped = _is_con_mapped(con);
}
/* remove the urgency hint of the workspace (if set) */
if (con->urgent) {
con->urgent = false;
con_update_parents_urgency(con);
workspace_update_urgent_flag(con_get_workspace(con));
}
/* Get the container which is next focused */
Con *next = con_next_focused(con);
DLOG("next = %p, focused = %p\n", next, focused);
@ -251,15 +258,28 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
free(con->window);
}
/* kill the X11 part of this container */
x_con_kill(con);
Con *ws = con_get_workspace(con);
/* Figure out which container to focus next before detaching 'con'. */
if (con_is_floating(con)) {
if (con == focused) {
DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
next = con_next_focused(parent);
dont_kill_parent = true;
DLOG("Alright, focusing %p\n", next);
} else {
next = NULL;
}
}
/* Detach the container so that it will not be rendered anymore. */
con_detach(con);
/* disable urgency timer, if needed */
if (con->urgency_timer != NULL) {
DLOG("Removing urgency timer of con %p\n", con);
workspace_update_urgent_flag(con_get_workspace(con));
workspace_update_urgent_flag(ws);
ev_timer_stop(main_loop, con->urgency_timer);
FREE(con->urgency_timer);
}
@ -270,21 +290,24 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
con_fix_percent(parent);
}
/* Render the tree so that the surrounding containers take up the space
* which 'con' does no longer occupy. If we dont render here, there will
* be a gap in our containers and that could trigger an EnterNotify for an
* underlying container, see ticket #660.
*
* Rendering has to be avoided when dont_kill_parent is set (when
* tree_close calls itself recursively) because the tree is in a
* non-renderable state during that time. */
if (!dont_kill_parent)
tree_render();
/* kill the X11 part of this container */
x_con_kill(con);
if (con_is_floating(con)) {
Con *ws = con_get_workspace(con);
DLOG("Container was floating, killing floating container\n");
tree_close(parent, DONT_KILL_WINDOW, false, (con == focused));
DLOG("parent container killed\n");
if (con == focused) {
DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
/* go down the focus stack as far as possible */
next = con_descend_focused(ws);
dont_kill_parent = true;
DLOG("Alright, focusing %p\n", next);
} else {
next = NULL;
}
}
free(con->name);
@ -350,17 +373,23 @@ void tree_close_con(kill_window_t kill_window) {
*
*/
void tree_split(Con *con, orientation_t orientation) {
/* for a workspace, we just need to change orientation */
if (con->type == CT_WORKSPACE) {
DLOG("Workspace, simply changing orientation to %d\n", orientation);
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
}
else if (con->type == CT_FLOATING_CON) {
if (con->type == CT_FLOATING_CON) {
DLOG("Floating containers can't be split.\n");
return;
}
if (con->type == CT_WORKSPACE) {
if (con_num_children(con) < 2) {
DLOG("Just changing orientation of workspace\n");
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
} else {
/* if there is more than one container on the workspace
* move them into a new container and handle this instead */
con = workspace_encapsulate(con);
}
}
Con *parent = con->parent;
/* Force re-rendering to make the indicator border visible. */

View File

@ -183,44 +183,6 @@ static char **append_argument(char **original, char *argument) {
return result;
}
/*
* Returns the name of a temporary file with the specified prefix.
*
*/
char *get_process_filename(const char *prefix) {
/* dir stores the directory path for this and all subsequent calls so that
* we only create a temporary directory once per i3 instance. */
static char *dir = NULL;
if (dir == NULL) {
/* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
if ((dir = getenv("XDG_RUNTIME_DIR"))) {
char *tmp;
sasprintf(&tmp, "%s/i3", dir);
dir = tmp;
if (!path_exists(dir)) {
if (mkdir(dir, 0700) == -1) {
perror("mkdir()");
return NULL;
}
}
} else {
/* If not, we create a (secure) temp directory using the template
* /tmp/i3-<user>.XXXXXX */
struct passwd *pw = getpwuid(getuid());
const char *username = pw ? pw->pw_name : "unknown";
sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
/* mkdtemp modifies dir */
if (mkdtemp(dir) == NULL) {
perror("mkdtemp()");
return NULL;
}
}
}
char *filename;
sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
return filename;
}
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
@ -304,8 +266,8 @@ char *store_restart_layout(void) {
void i3_restart(bool forget_layout) {
char *restart_filename = forget_layout ? NULL : store_restart_layout();
kill_configerror_nagbar(true);
kill_commanderror_nagbar(true);
kill_nagbar(&config_error_nagbar_pid, true);
kill_nagbar(&command_error_nagbar_pid, true);
restore_geometry();
@ -382,3 +344,103 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
}
#endif
/*
* Handler which will be called when we get a SIGCHLD for the nagbar, meaning
* it exited (or could not be started, depending on the exit code).
*
*/
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
ev_child_stop(EV_A_ watcher);
if (!WIFEXITED(watcher->rstatus)) {
ELOG("ERROR: i3-nagbar did not exit normally.\n");
return;
}
int exitcode = WEXITSTATUS(watcher->rstatus);
DLOG("i3-nagbar process exited with status %d\n", exitcode);
if (exitcode == 2) {
ELOG("ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
}
*((pid_t*)watcher->data) = -1;
}
/*
* Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
* SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
*
*/
static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
pid_t *nagbar_pid = (pid_t*)watcher->data;
if (*nagbar_pid != -1) {
LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, *nagbar_pid);
kill(*nagbar_pid, SIGKILL);
}
}
/*
* Starts an i3-nagbar instance with the given parameters. Takes care of
* handling SIGCHLD and killing i3-nagbar when i3 exits.
*
* The resulting PID will be stored in *nagbar_pid and can be used with
* kill_nagbar() to kill the bar later on.
*
*/
void start_nagbar(pid_t *nagbar_pid, char *argv[]) {
if (*nagbar_pid != -1) {
DLOG("i3-nagbar already running (PID %d), not starting again.\n", *nagbar_pid);
return;
}
*nagbar_pid = fork();
if (*nagbar_pid == -1) {
warn("Could not fork()");
return;
}
/* child */
if (*nagbar_pid == 0)
exec_i3_utility("i3-nagbar", argv);
DLOG("Starting i3-nagbar with PID %d\n", *nagbar_pid);
/* parent */
/* install a child watcher */
ev_child *child = smalloc(sizeof(ev_child));
ev_child_init(child, &nagbar_exited, *nagbar_pid, 0);
child->data = nagbar_pid;
ev_child_start(main_loop, child);
/* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
* still running) */
ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
ev_cleanup_init(cleanup, nagbar_cleanup);
cleanup->data = nagbar_pid;
ev_cleanup_start(main_loop, cleanup);
}
/*
* Kills the i3-nagbar process, if *nagbar_pid != -1.
*
* If wait_for_it is set (restarting i3), this function will waitpid(),
* otherwise, ev is assumed to handle it (reloading).
*
*/
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
if (*nagbar_pid == -1)
return;
if (kill(*nagbar_pid, SIGTERM) == -1)
warn("kill(configerror_nagbar) failed");
if (!wait_for_it)
return;
/* When restarting, we dont enter the ev main loop anymore and after the
* exec(), our old pid is no longer watched. So, ev wont handle SIGCHLD
* for us and we would end up with a <defunct> process. Therefore we
* waitpid() here. */
waitpid(*nagbar_pid, NULL, 0);
}

View File

@ -397,7 +397,7 @@ static void _workspace_show(Con *workspace) {
* the corresponding workspace is cleaned up.
* NOTE: Internal cons such as __i3_scratch (when a scratchpad window is
* focused) are skipped, see bug #868. */
if (current && !(current->name[0] == '_' && current->name[1] == '_')) {
if (current && !con_is_internal(current)) {
FREE(previous_workspace_name);
if (current) {
previous_workspace_name = sstrdup(current->name);

24
src/x.c
View File

@ -465,7 +465,7 @@ void x_draw_decoration(Con *con) {
xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
/* 5: draw two unconnected lines in border color */
/* 5: draw two unconnected horizontal lines in border color */
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
Rect *dr = &(con->deco_rect);
int deco_diff_l = 2;
@ -539,19 +539,21 @@ after_title:
* the right border again after rendering the text (and the unconnected
* lines in border color). */
/* Draw a separator line after every tab (except the last one), so that
* tabs can be easily distinguished. */
if (parent->layout == L_TABBED && TAILQ_NEXT(con, nodes) != NULL) {
/* Draw a 1px separator line before and after every tab, so that tabs can
* be easily distinguished. */
if (parent->layout == L_TABBED) {
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
} else {
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
}
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 4,
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6,
(xcb_point_t[]){
{ dr->x + dr->width, dr->y },
{ dr->x + dr->width, dr->y + dr->height },
{ dr->x + dr->width - 1, dr->y },
{ dr->x + dr->width - 1, dr->y + dr->height },
{ dr->x + dr->width - 2, dr->y },
{ dr->x + dr->width - 2, dr->y + dr->height }
{ dr->x, dr->y + dr->height },
{ dr->x, dr->y },
});
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
@ -918,8 +920,12 @@ void x_push_changes(Con *con) {
Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
Output *target = get_output_containing(mid_x, mid_y);
if (current != target)
if (current != target) {
/* Ignore MotionNotify events generated by warping */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
}
}
warp_to = NULL;
}
@ -955,7 +961,7 @@ void x_push_changes(Con *con) {
}
if (set_focus) {
DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus);
/* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
* no focus change events for our own focus changes. We only want
* these generated by the clients. */

10
testcases/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
testsuite-*
latest
Makefile
Makefile.old
.last_run_timings.json
_Inline
inc
META.yml
i3-cfg-for-*
-

View File

@ -1,10 +1,11 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
# © 2010-2011 Michael Stapelberg and contributors
# © 2010-2012 Michael Stapelberg and contributors
package complete_run;
use strict;
use warnings;
use v5.10;
use utf8;
# the following are modules which ship with Perl (>= 5.10):
use Pod::Usage;
use Cwd qw(abs_path);
@ -29,6 +30,9 @@ use AnyEvent::I3 qw(:all);
use X11::XCB::Connection;
use JSON::XS; # AnyEvent::I3 depends on it, too.
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
# Close superfluous file descriptors which were passed by running in a VIM
# subshell or situations like that.
AnyEvent::Util::close_all_fds_except(0, 1, 2);
@ -78,7 +82,7 @@ my @binaries = qw(
);
foreach my $binary (@binaries) {
die "$binary executable not found" unless -e $binary;
die "$binary executable not found, did you run “make”?" unless -e $binary;
die "$binary is not an executable" unless -x $binary;
}

View File

@ -53,6 +53,10 @@ sub activate_i3 {
$ENV{LISTEN_FDS} = 1;
delete $ENV{DESKTOP_STARTUP_ID};
delete $ENV{I3SOCK};
# $SHELL could be set to fish, which will horribly break running shell
# commands via i3s exec feature. This happened e.g. when having
# “set-option -g default-shell "/usr/bin/fish"” in ~/.tmux.conf
delete $ENV{SHELL};
unless ($args{dont_create_temp_dir}) {
$ENV{XDG_RUNTIME_DIR} = '/tmp/i3-testsuite/';
mkdir $ENV{XDG_RUNTIME_DIR};

View File

@ -529,10 +529,19 @@ sub get_ws_content {
Returns the container ID of the currently focused container on C<$workspace>.
Note that the container ID is B<not> the X11 window ID, so comparing the result
of C<get_focused> with a window's C<< ->{id} >> property does B<not> work.
my $ws = fresh_workspace;
my $first_window = open_window;
my $first_id = get_focused();
my $second_window = open_window;
is(get_focused($ws), $second_window, 'second window focused');
my $second_id = get_focused();
cmd 'focus left';
is(get_focused($ws), $first_id, 'second window focused');
=cut
sub get_focused {

View File

@ -79,6 +79,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
my $pid = launch_with_config($config);
exit_gracefully($pid);

View File

@ -17,220 +17,268 @@
use i3test i3_autostart => 0;
use List::Util qw(first);
my $_NET_WM_STATE_REMOVE = 0;
my $_NET_WM_STATE_ADD = 1;
my $_NET_WM_STATE_TOGGLE = 2;
sub set_urgency {
my ($win, $urgent_flag, $type) = @_;
if ($type == 1) {
$win->add_hint('urgency') if ($urgent_flag);
$win->delete_hint('urgency') if (!$urgent_flag);
} elsif ($type == 2) {
my $msg = pack "CCSLLLLLL",
X11::XCB::CLIENT_MESSAGE, # response_type
32, # format
0, # sequence
$win->id, # window
$x->atom(name => '_NET_WM_STATE')->id, # message type
($urgent_flag ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0]
$x->atom(name => '_NET_WM_STATE_DEMANDS_ATTENTION')->id, # data32[1]
0, # data32[2]
0, # data32[3]
0; # data32[4]
$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
}
}
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
force_display_urgency_hint 0ms
EOT
my $pid = launch_with_config($config);
my $tmp = fresh_workspace;
my $type;
for ($type = 1; $type <= 2; $type++) {
my $pid = launch_with_config($config);
my $tmp = fresh_workspace;
#####################################################################
# Create two windows and put them in stacking mode
#####################################################################
cmd 'split v';
cmd 'split v';
my $top = open_window;
my $bottom = open_window;
my $top = open_window;
my $bottom = open_window;
my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag');
my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag');
# cmd 'layout stacking';
#####################################################################
# Add the urgency hint, switch to a different workspace and back again
#####################################################################
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
my @content = @{get_ws_content($tmp)};
@urgent = grep { $_->{urgent} } @content;
my $top_info = first { $_->{window} == $top->id } @content;
my $bottom_info = first { $_->{window} == $bottom->id } @content;
my @content = @{get_ws_content($tmp)};
@urgent = grep { $_->{urgent} } @content;
my $top_info = first { $_->{window} == $top->id } @content;
my $bottom_info = first { $_->{window} == $bottom->id } @content;
ok($top_info->{urgent}, 'top window is marked urgent');
ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
is(@urgent, 1, 'exactly one window got the urgent flag');
ok($top_info->{urgent}, 'top window is marked urgent');
ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
is(@urgent, 1, 'exactly one window got the urgent flag');
cmd '[id="' . $top->id . '"] focus';
cmd '[id="' . $top->id . '"] focus';
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after focusing');
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after focusing');
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
#####################################################################
# Check if the workspace urgency hint gets set/cleared correctly
#####################################################################
my $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
my $ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace');
my $otmp = fresh_workspace;
my $otmp = fresh_workspace;
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
$ws = get_ws($tmp);
ok($ws->{urgent}, 'urgent flag set on workspace');
$ws = get_ws($tmp);
ok($ws->{urgent}, 'urgent flag set on workspace');
cmd "workspace $tmp";
cmd "workspace $tmp";
$ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
$ws = get_ws($tmp);
ok(!$ws->{urgent}, 'urgent flag not set on workspace after switching');
################################################################################
# Use the 'urgent' criteria to switch to windows which have the urgency hint set.
################################################################################
# Go to a new workspace, open a different window, verify focus is on it.
$otmp = fresh_workspace;
my $different_window = open_window;
is($x->input_focus, $different_window->id, 'new window focused');
$otmp = fresh_workspace;
my $different_window = open_window;
is($x->input_focus, $different_window->id, 'new window focused');
# Add the urgency hint on the other window.
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
# Now try to switch to that window and see if focus changes.
cmd '[urgent=latest] focus';
isnt($x->input_focus, $different_window->id, 'window no longer focused');
is($x->input_focus, $top->id, 'urgent window focused');
cmd '[urgent=latest] focus';
isnt($x->input_focus, $different_window->id, 'window no longer focused');
is($x->input_focus, $top->id, 'urgent window focused');
################################################################################
# Same thing, but with multiple windows and using the 'urgency=latest' criteria
# (verify that it works in the correct order).
################################################################################
cmd "workspace $otmp";
is($x->input_focus, $different_window->id, 'new window focused again');
cmd "workspace $otmp";
is($x->input_focus, $different_window->id, 'new window focused again');
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
$bottom->add_hint('urgency');
sync_with_i3;
set_urgency($bottom, 1, $type);
sync_with_i3;
cmd '[urgent=latest] focus';
is($x->input_focus, $bottom->id, 'latest urgent window focused');
$bottom->delete_hint('urgency');
sync_with_i3;
cmd '[urgent=latest] focus';
is($x->input_focus, $bottom->id, 'latest urgent window focused');
set_urgency($bottom, 0, $type);
sync_with_i3;
cmd '[urgent=latest] focus';
is($x->input_focus, $top->id, 'second urgent window focused');
$top->delete_hint('urgency');
sync_with_i3;
cmd '[urgent=latest] focus';
is($x->input_focus, $top->id, 'second urgent window focused');
set_urgency($top, 0, $type);
sync_with_i3;
################################################################################
# Same thing, but with multiple windows and using the 'urgency=oldest' criteria
# (verify that it works in the correct order).
################################################################################
cmd "workspace $otmp";
is($x->input_focus, $different_window->id, 'new window focused again');
cmd "workspace $otmp";
is($x->input_focus, $different_window->id, 'new window focused again');
$top->add_hint('urgency');
sync_with_i3;
set_urgency($top, 1, $type);
sync_with_i3;
$bottom->add_hint('urgency');
sync_with_i3;
set_urgency($bottom, 1, $type);
sync_with_i3;
cmd '[urgent=oldest] focus';
is($x->input_focus, $top->id, 'oldest urgent window focused');
$top->delete_hint('urgency');
sync_with_i3;
cmd '[urgent=oldest] focus';
is($x->input_focus, $top->id, 'oldest urgent window focused');
set_urgency($top, 0, $type);
sync_with_i3;
cmd '[urgent=oldest] focus';
is($x->input_focus, $bottom->id, 'oldest urgent window focused');
$bottom->delete_hint('urgency');
sync_with_i3;
cmd '[urgent=oldest] focus';
is($x->input_focus, $bottom->id, 'oldest urgent window focused');
set_urgency($bottom, 0, $type);
sync_with_i3;
################################################################################
# Check if urgent flag gets propagated to parent containers
################################################################################
cmd 'split v';
cmd 'split v';
sub count_urgent {
sub count_urgent {
my ($con) = @_;
my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
my $urgent = grep { $_->{urgent} } @children;
$urgent += count_urgent($_) for @children;
return $urgent;
}
}
$tmp = fresh_workspace;
$tmp = fresh_workspace;
my $win1 = open_window;
my $win2 = open_window;
cmd 'layout stacked';
cmd 'split vertical';
my $win3 = open_window;
my $win4 = open_window;
cmd 'split horizontal' ;
my $win5 = open_window;
my $win6 = open_window;
my $win1 = open_window;
my $win2 = open_window;
cmd 'layout stacked';
cmd 'split vertical';
my $win3 = open_window;
my $win4 = open_window;
cmd 'split horizontal' ;
my $win5 = open_window;
my $win6 = open_window;
sync_with_i3;
sync_with_i3;
my $urgent = count_urgent(get_ws($tmp));
is($urgent, 0, 'no window got the urgent flag');
my $urgent = count_urgent(get_ws($tmp));
is($urgent, 0, 'no window got the urgent flag');
cmd '[id="' . $win2->id . '"] focus';
sync_with_i3;
$win5->add_hint('urgency');
$win6->add_hint('urgency');
sync_with_i3;
cmd '[id="' . $win2->id . '"] focus';
sync_with_i3;
set_urgency($win5, 1, $type);
set_urgency($win6, 1, $type);
sync_with_i3;
# we should have 5 urgent cons. win5, win6 and their 3 split parents.
$urgent = count_urgent(get_ws($tmp));
is($urgent, 5, '2 windows and 3 split containers got the urgent flag');
$urgent = count_urgent(get_ws($tmp));
is($urgent, 5, '2 windows and 3 split containers got the urgent flag');
cmd '[id="' . $win5->id . '"] focus';
sync_with_i3;
cmd '[id="' . $win5->id . '"] focus';
sync_with_i3;
# now win5 and still the split parents should be urgent.
$urgent = count_urgent(get_ws($tmp));
is($urgent, 4, '1 window and 3 split containers got the urgent flag');
$urgent = count_urgent(get_ws($tmp));
is($urgent, 4, '1 window and 3 split containers got the urgent flag');
cmd '[id="' . $win6->id . '"] focus';
sync_with_i3;
cmd '[id="' . $win6->id . '"] focus';
sync_with_i3;
# now now window should be urgent.
$urgent = count_urgent(get_ws($tmp));
is($urgent, 0, 'All urgent flags got cleared');
$urgent = count_urgent(get_ws($tmp));
is($urgent, 0, 'All urgent flags got cleared');
################################################################################
# Regression test: Check that urgent floating containers work properly (ticket
# #821)
################################################################################
$tmp = fresh_workspace;
my $floating_win = open_floating_window;
$tmp = fresh_workspace;
my $floating_win = open_floating_window;
# switch away
fresh_workspace;
fresh_workspace;
$floating_win->add_hint('urgency');
sync_with_i3;
set_urgency($floating_win, 1, $type);
sync_with_i3;
cmd "workspace $tmp";
cmd "workspace $tmp";
does_i3_live;
does_i3_live;
exit_gracefully($pid);
###############################################################################
# Check if the urgency hint is still set when the urgent window is killed
###############################################################################
my $ws1 = fresh_workspace;
my $ws2 = fresh_workspace;
cmd "workspace $ws1";
my $w1 = open_window;
my $w2 = open_window;
cmd "workspace $ws2";
sync_with_i3;
set_urgency($w1, 1, $type);
sync_with_i3;
cmd '[id="' . $w1->id . '"] kill';
sync_with_i3;
my $w = get_ws($ws1);
is($w->{urgent}, 0, 'Urgent flag no longer set after killing the window ' .
'from another workspace');
exit_gracefully($pid);
}
done_testing;

View File

@ -158,4 +158,24 @@ is(get_output_content()->{layout}, 'splith', 'content container layout ok');
cmd 'layout stacked';
is(get_output_content()->{layout}, 'splith', 'content container layout still ok');
######################################################################
# Splitting a workspace that has more than one child
######################################################################
$tmp = fresh_workspace;
cmd 'open';
cmd 'open';
cmd 'focus parent';
cmd 'split v';
cmd 'open';
my $content = get_ws_content($tmp);
my $fst = $content->[0];
my $snd = $content->[1];
is(@{$content}, 2, 'two containers on workspace');
is(@{$fst->{nodes}}, 2, 'first child has two children');
is(@{$snd->{nodes}}, 0, 'second child has no children');
done_testing;

View File

@ -23,6 +23,7 @@ my $i3 = i3(get_socket_path());
# We move the pointer out of our way to avoid a bug where the focus will
# be set to the window under the cursor
$x->root->warp_pointer(0, 0);
sync_with_i3;
sub move_workspace_test {
my ($movecmd) = @_;

View File

@ -98,6 +98,7 @@ my $workspaces = get_workspace_names;
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
$window = open_special;
sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
@ -157,6 +158,7 @@ $workspaces = get_workspace_names;
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
$window = open_special;
sync_with_i3;
my $content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
@ -205,6 +207,7 @@ is(@docked, 0, 'one dock client yet');
$window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
);
sync_with_i3;
$content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');

View File

@ -73,6 +73,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
ok(get_ws($tmp)->{focused}, 'current workspace focused');
my $window = open_special;
sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');

View File

@ -21,6 +21,10 @@ use i3test;
use POSIX qw(mkfifo);
use File::Temp qw(:POSIX);
SKIP: {
skip "X11::XCB too old (need >= 0.07)", 24 if $X11::XCB::VERSION < 0.07;
use ExtUtils::PkgConfig;
# setup dependency on libstartup-notification using pkg-config
@ -42,16 +46,10 @@ static SnDisplay *sndisplay;
static SnLauncheeContext *ctx;
static xcb_connection_t *conn;
// TODO: this should use $x
void init_ctx() {
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
errx(1, "x11 conn failed");
printf("screen = %d\n", screen);
void init_ctx(void *connptr) {
conn = (xcb_connection_t*)connptr;
sndisplay = sn_xcb_display_new(conn, NULL, NULL);
ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
ctx = sn_launchee_context_new_from_environment(sndisplay, 0);
}
const char *get_startup_id() {
@ -101,7 +99,7 @@ isnt($startup_id, '', 'startup_id not empty');
$ENV{DESKTOP_STARTUP_ID} = $startup_id;
# Create a new libstartup-notification launchee context
init_ctx();
init_ctx($x->get_xcb_conn());
# Make sure the context was set up successfully
is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
@ -211,5 +209,6 @@ close($fh);
unlink($tmp);
is($startup_id, '', 'startup_id empty');
}
done_testing;

View File

@ -93,8 +93,7 @@ is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
################################################################################
# 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
# workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The windows size
# and position will be changed (once!) on the next 'scratchpad show' and the
# flag will be changed to SCRATCHPAD_CHANGED.
# and position will be changed on the next 'scratchpad show'.
################################################################################
my ($nodes, $focus) = get_ws_content($tmp);
@ -165,10 +164,33 @@ $__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
################################################################################
# 6: Resizing the window should disable auto centering on scratchpad show
################################################################################
cmd 'scratchpad show';
$ws = get_ws($tmp);
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'fresh',
'scratchpad_state fresh');
cmd 'resize grow width 10 px';
cmd 'scratchpad show';
cmd 'scratchpad show';
$ws = get_ws($tmp);
$scratchrect = $ws->{floating_nodes}->[0]->{rect};
$outputrect = $output->{rect};
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed',
'scratchpad_state changed');
is($scratchrect->{width}, $outputrect->{width} * 0.5 + 10, 'scratch width is 50% + 10px');
cmd 'resize shrink width 10 px';
cmd 'scratchpad show';
################################################################################
# 6: Verify that repeated 'scratchpad show' cycle through the stack, that is,
# 7: Verify that repeated 'scratchpad show' cycle through the stack, that is,
# toggling a visible window should insert it at the bottom of the stack of the
# __i3_scratch workspace.
################################################################################
@ -216,39 +238,6 @@ cmd 'scratchpad show';
isnt(get_focused($tmp), $fresh_id, 'focus changed');
################################################################################
# 7: Verify that using scratchpad show with criteria works as expected:
# When matching a scratchpad window which is visible, it should hide it.
# When matching a scratchpad window which is on __i3_scratch, it should show it.
# When matching a non-scratchpad window, it should be a no-op.
################################################################################
# Verify that using 'scratchpad show' without any matching windows is a no-op.
$old_focus = get_focused($tmp);
cmd '[title="nomatch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
# Verify that we can use criteria to show a scratchpad window.
cmd '[title="scratch-match"] scratchpad show';
my $scratch_focus = get_focused($tmp);
isnt($scratch_focus, $old_focus, 'matching criteria works');
cmd '[title="scratch-match"] scratchpad show';
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
is(get_focused($tmp), $old_focus, 'focus restored');
# Verify that we cannot use criteria to show a non-scratchpad window.
my $tmp2 = fresh_workspace;
my $non_scratch_window = open_window(name => 'non-scratch');
cmd "workspace $tmp";
is(get_focused($tmp), $old_focus, 'focus still ok');
cmd '[title="non-match"] scratchpad show';
is(get_focused($tmp), $old_focus, 'focus unchanged');
################################################################################
# 8: Show it, move it around, hide it. Verify that the position is retained
# when showing it again.
@ -369,6 +358,92 @@ verify_scratchpad_move_multiple_win(0);
$tmp = fresh_workspace;
verify_scratchpad_move_multiple_win(1);
################################################################################
# 12: open a scratchpad window on a workspace, switch to another workspace and
# call 'scratchpad show' again
################################################################################
sub verify_scratchpad_move_with_visible_scratch_con {
my ($first, $second, $cross_output) = @_;
cmd "workspace $first";
my $window1 = open_window;
cmd 'move scratchpad';
my $window2 = open_window;
cmd 'move scratchpad';
# this should bring up window 1
cmd 'scratchpad show';
my $ws = get_ws($first);
is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
# this should bring up window 1
cmd "workspace $second";
cmd 'scratchpad show';
is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
my $ws2 = get_ws($second);
is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
unless ($cross_output) {
ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
} else {
$ws = get_ws($first);
is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
}
# hide window 1 again
cmd 'move scratchpad';
# this should bring up window 2
cmd "workspace $first";
cmd 'scratchpad show';
is($x->input_focus, $window2->id, "showed the correct scratchpad window");
}
# let's clear the scratchpad first
sub clear_scratchpad {
while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
cmd 'scratchpad show';
cmd 'kill';
}
}
clear_scratchpad;
is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
my ($first, $second);
$first = fresh_workspace;
$second = fresh_workspace;
verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
does_i3_live;
################################################################################
# 13: Test whether scratchpad show moves focus to the scratchpad window
# when another window on the same workspace has focus
################################################################################
clear_scratchpad;
my $ws = fresh_workspace;
open_window;
my $scratch = get_focused($ws);
cmd 'move scratchpad';
cmd 'scratchpad show';
open_window;
my $not_scratch = get_focused($ws);
is(get_focused($ws), $not_scratch, 'not scratch window has focus');
cmd 'scratchpad show';
is(get_focused($ws), $scratch, 'scratchpad is focused');
# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
done_testing;

View File

@ -146,6 +146,7 @@ $window = open_floating_window(rect => [ 0, 0, 100, 100 ]);
cmd 'border none';
cmd 'resize shrink height 80px or 80ppt';
cmd 'resize shrink width 80px or 80ppt';
sync_with_i3;
$rect = $window->rect;
is($rect->{width}, 60, 'width = 60');
is($rect->{height}, 50, 'height = 50');
@ -170,6 +171,7 @@ $window = open_floating_window(rect => [ 200, 200, 50, 50 ]);
cmd 'border none';
cmd 'resize grow height 100px or 100ppt';
cmd 'resize grow width 100px or 100ppt';
sync_with_i3;
$rect = $window->rect;
is($rect->{width}, 100, 'width = 100');
is($rect->{height}, 100, 'height = 100');
@ -177,6 +179,7 @@ is($rect->{height}, 100, 'height = 100');
my $old_x = $rect->{x};
my $old_y = $rect->{y};
cmd 'resize grow up 10px or 10ppt';
sync_with_i3;
$rect = $window->rect;
is($rect->{x}, $old_x, 'window did not move when trying to resize');
is($rect->{y}, $old_y, 'window did not move when trying to resize');

View File

@ -26,7 +26,7 @@ sub parser_calls {
my $stdout;
run [ '../test.config_parser', $command ],
'>&-',
'>/dev/null',
'2>', \$stdout;
# TODO: use a timeout, so that we can error out if it doesnt terminate
@ -144,6 +144,27 @@ is(parser_calls($config),
$expected,
'floating_minimum_size ok');
################################################################################
# popup_during_fullscreen
################################################################################
$config = <<'EOT';
popup_during_fullscreen ignore
popup_during_fullscreen leave_fullscreen
popup_during_fullscreen SMArt
EOT
$expected = <<'EOT';
cfg_popup_during_fullscreen(ignore)
cfg_popup_during_fullscreen(leave_fullscreen)
cfg_popup_during_fullscreen(smart)
EOT
is(parser_calls($config),
$expected,
'popup_during_fullscreen ok');
################################################################################
# floating_modifier
################################################################################
@ -391,8 +412,11 @@ hide_edge_border both
client.focused #4c7899 #285577 #ffffff #2e9ef4
EOT
$expected = <<'EOT';
my $expected_all_tokens = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
EOT
my $expected_end = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: hide_edge_border both
ERROR: CONFIG: ^^^^^^^^^^^^^^^^^^^^^
@ -400,6 +424,8 @@ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4
cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4)
EOT
$expected = $expected_all_tokens . $expected_end;
is(parser_calls($config),
$expected,
'errors dont harm subsequent statements');
@ -438,9 +464,11 @@ unknown qux
# this should not show up
EOT
$expected = <<'EOT';
my $expected_head = <<'EOT';
cfg_font(foobar)
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
EOT
my $expected_tail = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 3: font foobar
ERROR: CONFIG: Line 4:
@ -450,6 +478,8 @@ ERROR: CONFIG: Line 6:
ERROR: CONFIG: Line 7: # yay
EOT
$expected = $expected_head . $expected_all_tokens . $expected_tail;
is(parser_calls($config),
$expected,
'error message (2+2 context) ok');
@ -462,13 +492,14 @@ $config = <<'EOT';
unknown qux
EOT
$expected = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
$expected_tail = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: unknown qux
ERROR: CONFIG: ^^^^^^^^^^^
EOT
$expected = $expected_all_tokens . $expected_tail;
is(parser_calls($config),
$expected,
'error message (0+0 context) ok');
@ -482,14 +513,15 @@ $config = <<'EOT';
unknown qux
EOT
$expected = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
$expected_tail = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: # context before
ERROR: CONFIG: Line 2: unknown qux
ERROR: CONFIG: ^^^^^^^^^^^
EOT
$expected = $expected_all_tokens . $expected_tail;
is(parser_calls($config),
$expected,
'error message (1+0 context) ok');
@ -503,14 +535,15 @@ unknown qux
# context after
EOT
$expected = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
$expected_tail = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: unknown qux
ERROR: CONFIG: ^^^^^^^^^^^
ERROR: CONFIG: Line 2: # context after
EOT
$expected = $expected_all_tokens . $expected_tail;
is(parser_calls($config),
$expected,
'error message (0+1 context) ok');
@ -525,8 +558,7 @@ unknown qux
# context 2 after
EOT
$expected = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
$expected_tail = <<'EOT';
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: unknown qux
ERROR: CONFIG: ^^^^^^^^^^^
@ -534,6 +566,8 @@ ERROR: CONFIG: Line 2: # context after
ERROR: CONFIG: Line 3: # context 2 after
EOT
$expected = $expected_all_tokens . $expected_tail;
is(parser_calls($config),
$expected,
'error message (0+2 context) ok');

View File

@ -0,0 +1,58 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verifies that using criteria to address scratchpad windows works.
use i3test;
################################################################################
# Verify that using scratchpad show with criteria works as expected:
# When matching a scratchpad window which is visible, it should hide it.
# When matching a scratchpad window which is on __i3_scratch, it should show it.
# When matching a non-scratchpad window, it should be a no-op.
################################################################################
my $tmp = fresh_workspace;
my $third_window = open_window(name => 'scratch-match');
cmd 'move scratchpad';
# Verify that using 'scratchpad show' without any matching windows is a no-op.
my $old_focus = get_focused($tmp);
cmd '[title="nomatch"] scratchpad show';
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
# Verify that we can use criteria to show a scratchpad window.
cmd '[title="scratch-match"] scratchpad show';
my $scratch_focus = get_focused($tmp);
isnt($scratch_focus, $old_focus, 'matching criteria works');
cmd '[title="scratch-match"] scratchpad show';
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
is(get_focused($tmp), $old_focus, 'focus restored');
# Verify that we cannot use criteria to show a non-scratchpad window.
my $tmp2 = fresh_workspace;
my $non_scratch_window = open_window(name => 'non-scratch');
cmd "workspace $tmp";
is(get_focused($tmp), $old_focus, 'focus still ok');
cmd '[title="non-match"] scratchpad show';
is(get_focused($tmp), $old_focus, 'focus unchanged');
done_testing;

View File

@ -0,0 +1,65 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Moves the last window of a workspace to the scratchpad. The workspace will be
# cleaned up and previously, the subsequent focusing of a destroyed container
# would crash i3.
# Ticket: #913
# Bug still in: 4.4-97-gf767ac3
use i3test;
use X11::XCB qw(:all);
# TODO: move to X11::XCB
sub set_wm_class {
my ($id, $class, $instance) = @_;
# Add a _NET_WM_STRUT_PARTIAL hint
my $atomname = $x->atom(name => 'WM_CLASS');
my $atomtype = $x->atom(name => 'STRING');
$x->change_property(
PROP_MODE_REPLACE,
$id,
$atomname->id,
$atomtype->id,
8,
length($class) + length($instance) + 2,
"$instance\x00$class\x00"
);
}
sub open_special {
my %args = @_;
my $wm_class = delete($args{wm_class}) || 'special';
return open_window(
%args,
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
);
}
my $tmp = fresh_workspace;
# Open a new window which we can identify later on based on its WM_CLASS.
my $scratch = open_special;
my $tmp2 = fresh_workspace;
cmd '[class="special"] move scratchpad';
does_i3_live;
done_testing;

View File

@ -0,0 +1,49 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
use i3test;
SKIP: {
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Window event
################################
# Events
my $new = AnyEvent->condvar;
$i3->subscribe({
window => sub {
my ($event) = @_;
$new->send($event->{change} eq 'new');
}
})->recv;
open_window;
my $t;
$t = AnyEvent->timer(after => 0.5, cb => sub { $new->send(0); });
ok($new->recv, 'Window "new" event received');
}
done_testing;

View File

@ -61,8 +61,18 @@ open_window;
# output 2: 2
cmd 'workspace 1';
cmd 'workspace next';
# We need to sync after changing focus to a different output to wait for the
# EnterNotify to be processed, otherwise it will be processed at some point
# later in time and mess up our subsequent tests.
sync_with_i3;
is(focused_ws, '2', 'workspace 2 focused');
cmd 'workspace next';
# We need to sync after changing focus to a different output to wait for the
# EnterNotify to be processed, otherwise it will be processed at some point
# later in time and mess up our subsequent tests.
sync_with_i3;
is(focused_ws, '5', 'workspace 5 focused');
################################################################################
@ -81,11 +91,9 @@ cmd 'workspace prev_on_output';
is(focused_ws, '1', 'workspace 1 focused');
cmd 'workspace 2';
# XXX: This is to avoid EnterNotifies changing the focus. Not sure why they
# appear sometimes in the first place. Only happens when running the full
# testsuite.
$x->root->warp_pointer(1025, 0);
# We need to sync after changing focus to a different output to wait for the
# EnterNotify to be processed, otherwise it will be processed at some point
# later in time and mess up our subsequent tests.
sync_with_i3;
cmd 'workspace prev_on_output';

View File

@ -114,6 +114,16 @@ cmd 'move workspace to output left';
($x0, $x1) = workspaces_per_screen();
ok('5' ~~ @$x0, 'workspace 5 back on fake-0');
# Verify that wrapping works
cmd 'move workspace to output left';
($x0, $x1) = workspaces_per_screen();
ok('5' ~~ @$x1, 'workspace 5 on fake-1');
# Put workspace 5 where it should
cmd 'move workspace to output left';
($x0, $x1) = workspaces_per_screen();
ok('5' ~~ @$x0, 'workspace 5 on fake-0 again');
################################################################################
# Verify that coordinates of floating windows are fixed correctly when moving a
# workspace to a different output.

View File

@ -39,22 +39,22 @@ cmd 'floating toggle';
# Focus screen 1
$x->root->warp_pointer(1025, 0);
my $s1_ws = fresh_workspace;
sync_with_i3;
my $s1_ws = fresh_workspace;
my $fourth = open_window;
# Focus screen 2
$x->root->warp_pointer(0, 769);
my $s2_ws = fresh_workspace;
sync_with_i3;
my $s2_ws = fresh_workspace;
my $fifth = open_window;
# Focus screen 3
$x->root->warp_pointer(1025, 769);
my $s3_ws = fresh_workspace;
sync_with_i3;
my $s3_ws = fresh_workspace;
my $sixth = open_window;
my $seventh = open_window;

View File

@ -14,9 +14,10 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Regression test: Verify that focus is correct after moving a floating window
# to a workspace on a different visible output.
# Bug still in: 4.3-83-ge89a25f
# Verifies that moving containers wraps across outputs.
# E.g. when you have a container on the right output and you move it to the
# right, it should appear on the left output.
# Bug still in: 4.4-106-g3cd4b8c
use i3test i3_autostart => 0;
# Ensure the pointer is at (0, 0) so that we really start on the first
@ -29,17 +30,26 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
my $pid = launch_with_config($config);
my $left_ws = fresh_workspace(output => 0);
open_window;
my $right = fresh_workspace(output => 1);
my $left = fresh_workspace(output => 0);
my $right_ws = fresh_workspace(output => 1);
open_window;
my $right_float = open_floating_window;
my $win = open_window;
cmd "move workspace $left_ws";
is($x->input_focus, $right_float->id, 'floating window still focused');
is_num_children($left, 1, 'one container on left workspace');
cmd 'move container to output right';
cmd 'focus output right';
is_num_children($left, 0, 'no containers on left workspace');
is_num_children($right, 1, 'one container on right workspace');
cmd 'move container to output right';
is_num_children($left, 1, 'one container on left workspace');
is_num_children($right, 0, 'no containers on right workspace');
exit_gracefully($pid);

View File

@ -0,0 +1,97 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Tests whether moving workspaces between outputs works correctly.
use i3test i3_autostart => 0;
use List::Util qw(first);
# Ensure the pointer is at (0, 0) so that we really start on the first
# (the left) workspace.
$x->root->warp_pointer(0, 0);
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0,1024x768+1024+0
EOT
my $pid = launch_with_config($config);
sub workspaces_per_screen {
my $i3 = i3(get_socket_path());
my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}};
my $fake0 = first { $_->{name} eq 'fake-0' } @outputs;
my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}};
my $fake1 = first { $_->{name} eq 'fake-1' } @outputs;
my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}};
my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}};
my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}};
return \@fake0_workspaces, \@fake1_workspaces;
}
# Switch to temporary workspaces on both outputs so the numbers are free.
my $tmp_right = fresh_workspace(output => 1);
my $tmp_left = fresh_workspace(output => 0);
cmd 'workspace 1';
# Keep that workspace open.
my $win1 = open_window;
cmd 'workspace 5';
# Keep that workspace open.
open_window;
cmd "workspace $tmp_right";
cmd 'workspace 2';
# Keep that workspace open.
open_window;
my ($x0, $x1) = workspaces_per_screen();
is_deeply($x0, [ '1', '5' ], 'workspace 1 and 5 on fake-0');
is_deeply($x1, [ '2' ], 'workspace 2 on fake-1');
cmd 'workspace 1';
my ($nodes, $focus) = get_ws_content('1');
is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
cmd 'move workspace next';
cmd '[id="' . $win1->id . '"] focus';
($nodes, $focus) = get_ws_content('2');
is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 2 after moving');
cmd 'move workspace prev';
cmd '[id="' . $win1->id . '"] focus';
($nodes, $focus) = get_ws_content('1');
is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
cmd 'move workspace next_on_output';
cmd '[id="' . $win1->id . '"] focus';
($nodes, $focus) = get_ws_content('5');
is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 5 after moving');
exit_gracefully($pid);
done_testing;