Merge branch 'next'

This commit is contained in:
Michael Stapelberg 2013-12-22 21:14:21 +01:00
commit 28939365cb
80 changed files with 1546 additions and 797 deletions

2
.gitignore vendored
View File

@ -6,6 +6,7 @@ include/all.h.pch
*.swp
*.gcda
*.gcno
*.dSYM
test.commands_parser
test.config_parser
testcases/MYMETA.json
@ -31,3 +32,4 @@ docs/*.html
!/docs/refcard.html
i3-command-parser.stamp
i3-config-parser.stamp
.clang_complete

View File

@ -10,13 +10,13 @@
│ 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/ │
│ util-cursor³│ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/ │
│ 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/ │
│ Pod::Simple²│ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/
│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification
@ -26,6 +26,8 @@
¹ libsn = libstartup-notification
² Pod::Simple is a Perl module required for converting the testsuite
documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple
³ xcb-util-cursor, to be precise. Might be considered part of xcb-util, or not
:-).
i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
new dependencies.

82
RELEASE-NOTES-4.7 Normal file
View File

@ -0,0 +1,82 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.7 │
└──────────────────────────────┘
This is the i3 v4.7. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
This release features a number of documentation improvements, better error
messages in various places, better tray compatibility in i3bar, and a number of
bugfixes.
Relevant from a packaging point of view is that we have switched to the new
xcb-util-cursor library to get rid of libXcursor. The last remaining big piece
of Xlib code now is XKB, which we may be able to tackle in upcoming releases
thanks to the just released libxcb 1.10.
┌────────────────────────────┐
│ Changes in v4.7 │
└────────────────────────────┘
• docs/userguide: clarify variable parsing
• docs/userguide: clarify urgent_workspace
• docs/userguide: add proper quoting for rename sample command
• docs/userguide: clarify multiple criteria
• docs/userguide: userguide: explain the difference between comma and semicolon for command chaining
• docs/hacking-howto: update to reflect parser changes
• man/i3-dump-log: document -f
• switch from libXcursor to xcb-util-cursor
• Respect workspace numbers when looking for a free workspace name
• Revert "raise fullscreen windows on top of all other X11 windows"
• i3bar: Create pixmaps using the real bar height, rather than screen height
• Add scratchpad bindings to the default config
• Close all children when closing a workspace
• i3bar: Add new bar.binding_mode_indicator configuration
• Improve error message when $XDG_RUNTIME_DIR is not writable
• libi3/font: Draw the text at the expected place
• libi3/font: Set DPI for the pango context
• Add ability to escape out of a mouse-resize operation
• Do not resize/reposition floating containers when moving them to scratchpad
• i3-nagbar: Set button inner-width to the width of the label
• Assigned windows open urgent when not visible
• i3bar: Only configure tray on own outputs
• Command 'move <direction>' moves across outputs
• i3bar: Handle DestroyNotify events
• i3bar: Realign tray clients on map/unmap notify
• i3bar: Group child processes for signalling
• i3bar: Print error message when status_command fails
• Remove references to PATH_MAX macro for GNU/Hurd
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• update root geometry on output changes for “fullscreen global”
• dont flatten tabbed/stacked containers
• Fix handling of new windows with WM_STATE_FULLSCREEN
• correctly recognize assigned windows as urgent
• Fix keyboard and mouse resize in nested containers
• Reply to _NET_REQUEST_FRAME_EXTENTS correctly
• Fix command parser: resizing tiling windows
• Fix output retrieval for floating cons
• Use _PATH_BSHELL to ensure using a bourne shell
• Instead of crashing, return DRAG_ABORT on UnmapNotify from drag_pointer
• Remove-child callback skips output content cons
• ignore _NET_ACTIVE_WINDOW for scratchpad windows
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Alexander Neumann, badboy, Baptiste Daroussin, Bas Pape, Deiz, Franck Michea,
Jean-Philippe Ouellet, jj, jookia, kaersten, Lancelot SIX, Leo Gaspard,
mistnim, Peter Maatman, Quentin Glidic, Sebastian Ullrich, Slava, syl20bnr,
Tony Crisci, Trung Ngo, Vivien Didelot, Xarthisius
I want to specifically thank Tony Crisci for the very valuable help with
responding to bugreports in our bugtracker. Thank you!
-- Michael Stapelberg, 2013-12-22

View File

@ -2,8 +2,6 @@ UNAME=$(shell uname)
DEBUG=1
COVERAGE=0
INSTALL=install
FLEX=flex
BISON=bison
ifndef PREFIX
PREFIX=/usr
endif
@ -111,8 +109,8 @@ X11_CFLAGS := $(call cflags_for_lib, x11)
X11_LIBS := $(call ldflags_for_lib, x11,X11)
# Xcursor
XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor)
XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor)
XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor)
XCURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor)
# yajl
YAJL_CFLAGS := $(call cflags_for_lib, yajl)
@ -144,7 +142,7 @@ PANGO_LIBS := $(call ldflags_for_lib, cairo)
PANGO_LIBS += $(call ldflags_for_lib, pangocairo)
# libi3
LIBS = -L$(TOPDIR) -li3
LIBS = -L$(TOPDIR) -li3 -lm
## Platform-specific flags

20
debian/changelog vendored
View File

@ -1,8 +1,22 @@
i3-wm (4.5.2-1) experimental; urgency=low
i3-wm (4.6.1-1) unstable; urgency=low
* NOT YET RELEASED
* NOT YET RELEASED.
-- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 23:01:30 +0100
-- Michael Stapelberg <stapelberg@debian.org> Wed, 07 Aug 2013 20:53:26 +0200
i3-wm (4.6-1) unstable; urgency=low
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Wed, 07 Aug 2013 20:53:26 +0200
i3-wm (4.5.1-2) unstable; urgency=low
* experimental to unstable because i3-wm 4.5.1 was only in experimental due
to the freeze.
* bump standards-version to 3.9.4 (no changes necessary)
-- Michael Stapelberg <stapelberg@debian.org> Tue, 14 May 2013 20:48:16 +0200
i3-wm (4.5.1-1) experimental; urgency=low

4
debian/control vendored
View File

@ -9,7 +9,7 @@ Build-Depends: debhelper (>= 7.0.50~),
libxcb-xinerama0-dev (>= 1.1),
libxcb-randr0-dev,
libxcb-icccm4-dev,
libxcursor-dev,
libxcb-cursor-dev,
asciidoc (>= 8.4.4),
xmlto,
docbook-xml,
@ -21,7 +21,7 @@ Build-Depends: debhelper (>= 7.0.50~),
libcairo2-dev,
libpango1.0-dev,
libpod-simple-perl
Standards-Version: 3.9.3
Standards-Version: 3.9.4
Homepage: http://i3wm.org/
Package: i3

2
debian/rules vendored
View File

@ -38,7 +38,7 @@ override_dh_auto_build:
$(MAKE) -C docs
override_dh_installchangelogs:
dh_installchangelogs RELEASE-NOTES-4.5.1
dh_installchangelogs RELEASE-NOTES-4.6
override_dh_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install

View File

@ -97,21 +97,18 @@ Contains forward definitions for all public functions, as well as
doxygen-compatible comments (so if you want to get a bit more of the big
picture, either browse all header files or use doxygen if you prefer that).
src/cfgparse.l::
Contains the lexer for i3s configuration file, written for +flex(1)+.
src/cfgparse.y::
Contains the parser for i3s configuration file, written for +bison(1)+.
src/config_parser.c::
Contains a custom configuration parser. See src/command_parser.c for rationale
on why we use a custom parser.
src/click.c::
Contains all functions which handle mouse button clicks (right mouse button
clicks initiate resizing and thus are relatively complex).
src/cmdparse.l::
Contains the lexer for i3 commands, written for +flex(1)+.
src/cmdparse.y::
Contains the parser for i3 commands, written for +bison(1)+.
src/command_parser.c::
Contains a hand-written parser to parse commands (commands are what
you bind on keys and what you can send to i3 using the IPC interface, like
'move left' or 'workspace 4').
src/con.c::
Contains all functions which deal with containers directly (creating

View File

@ -494,6 +494,8 @@ font (string)::
The font to use for text on the bar.
workspace_buttons (boolean)::
Display workspace buttons or not? Defaults to true.
binding_mode_indicator (boolean)::
Display the mode indicator or not? Defaults to true.
verbose (boolean)::
Should the bar enable verbose output for debugging? Defaults to false.
colors (map)::
@ -539,6 +541,7 @@ urgent_workspace_text/urgent_workspace_bar::
"status_command": "i3status",
"font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1",
"workspace_buttons": true,
"binding_mode_indicator": true,
"verbose": false,
"colors": {
"background": "#c0c0c0",

View File

@ -252,7 +252,7 @@ workspace node. By default, the workspace nodes orientation is +horizontal+.
Now you move one of these terminals down (+$mod+k+ by default). The workspace
nodes orientation will be changed to +vertical+. The terminal window you moved
down is directly attached to the workspace and appears on the bottom of the
screen. A new (horizontal) container was created to accomodate the other two
screen. A new (horizontal) container was created to accommodate the other two
terminal windows. You will notice this when switching to tabbed mode (for
example). You would end up having one tab called "another container" and the
other one being the terminal window you moved down.
@ -446,7 +446,7 @@ New workspaces get a reasonable default orientation: Wide-screen monitors
(anything higher than wide) get vertical orientation.
With the +default_orientation+ configuration directive, you can override that
behaviour.
behavior.
*Syntax*:
----------------------------------------------
@ -570,11 +570,12 @@ set $m Mod1
bindsym $m+Shift+r restart
------------------------
Variables are directly replaced in the file when parsing. There is no fancy
handling and there are absolutely no plans to change this. If you need a more
dynamic configuration you should create a little script which generates a
configuration file and run it before starting i3 (for example in your
+~/.xsession+ file).
Variables are directly replaced in the file when parsing. Variables expansion
is not recursive so it is not possible to define a variable with a value
containing another variable. There is no fancy handling and there are
absolutely no plans to change this. If you need a more dynamic configuration
you should create a little script which generates a configuration file and run
it before starting i3 (for example in your +~/.xsession+ file).
=== Automatically putting clients on specific workspaces
@ -833,7 +834,7 @@ popup_during_fullscreen smart
When being in a tabbed or stacked container, the first container will be
focused when you use +focus down+ on the last container -- the focus wraps. If
however there is another stacked/tabbed container in that direction, focus will
be set on that container. This is the default behaviour so you can navigate to
be set on that container. This is the default behavior so you can navigate to
all your windows without having to use +focus parent+.
If you want the focus to *always* wrap and you are aware of using +focus
@ -899,7 +900,7 @@ workspace_auto_back_and_forth yes
If an application on another workspace sets an urgency hint, switching to this
workspace may lead to immediate focus of the application, which also means the
window decoration color would be immediately resetted to +client.focused+. This
window decoration color would be immediately reseted to +client.focused+. This
may make it unnecessarily hard to tell which window originally raised the
event.
@ -1179,11 +1180,32 @@ workspace_buttons <yes|no>
--------------------------
*Example*:
--------------------
------------------------
bar {
workspace_buttons no
}
--------------------
------------------------
=== Binding Mode indicator
Specifies whether the current binding mode indicator should be shown or not.
This is useful if you want to hide the workspace buttons but still be able
to see the current binding mode indicator.
For an example of a +mode+ definition, see <<resizingconfig>>.
The default is to show the mode indicator.
*Syntax*:
-------------------------------
binding_mode_indicator <yes|no>
-------------------------------
*Example*:
-----------------------------
bar {
binding_mode_indicator no
}
-----------------------------
=== Colors
@ -1210,7 +1232,7 @@ inactive_workspace::
will be the case for most workspaces.
urgent_workspace::
Border, background and text color for a workspace button when the workspace
window with the urgency hint set.
contains a window with the urgency hint set. Also applies to +mode+ indicators.
*Syntax*:
----------------------------------------
@ -1263,16 +1285,28 @@ bindsym $mod+x move container to workspace 3; workspace 3
[[command_criteria]]
Furthermore, you can change the scope of a command - that is, which containers
should be affected by that command, by using various criteria. These are
prefixed in square brackets to every command. If you want to kill all windows
which have the class Firefox, use:
should be affected by that command, by using various criteria. The criteria
are specified before any command in a pair of square brackets and are separated
by space.
When using multiple commands, separate them by using a +,+ (a comma) instead of
a semicolon. Criteria apply only until the next semicolon, so if you use a
semicolon to separate commands, only the first one will be executed for the
matched window(s).
*Example*:
------------------------------------
# if you want to kill all windows which have the class Firefox, use:
bindsym $mod+x [class="Firefox"] kill
# same thing, but case-insensitive
bindsym $mod+x [class="(?i)firefox"] kill
# kill only the About dialog from Firefox
bindsym $mod+x [class="Firefox" window_role="About"] kill
# enable floating mode and move container to workspace 4
for_window [class="^evil-app$"] floating enable, move container to workspace 4
------------------------------------
The criteria which are currently implemented are:
@ -1553,7 +1587,7 @@ specify a default name if there's currently no workspace starting with a "1".
You can rename workspaces. This might be useful to start with the default
numbered workspaces, do your work, and rename the workspaces afterwards to
reflect whats actually on them. You can also omit the old name to rename
the currently focused workspace. This is handy if you wan't to use the
the currently focused workspace. This is handy if you want to use the
rename command with +i3-input+.
*Syntax*:
@ -1568,7 +1602,7 @@ i3-msg 'rename workspace 5 to 6'
i3-msg 'rename workspace 1 to "1: www"'
i3-msg 'rename workspace "1: www" to "10: www"'
i3-msg 'rename workspace to "2: mail"
bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: '
bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: '
--------------------------------------------------------------------------
=== Moving workspaces to a different screen

View File

@ -791,17 +791,17 @@ int main(int argc, char *argv[]) {
close(fd);
unlink(config_path);
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
errx(1, "Cannot open display\n");
if (socket_path == NULL)
socket_path = root_atom_contents("I3_SOCKET_PATH");
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
if (socket_path == NULL)
socket_path = "/tmp/i3-ipc.sock";
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
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);
@ -813,7 +813,7 @@ int main(int argc, char *argv[]) {
#include "atoms.xmacro"
#undef xmacro
root_screen = xcb_aux_get_screen(conn, screens);
root_screen = xcb_aux_get_screen(conn, screen);
root = root_screen->root;
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))

View File

@ -90,7 +90,7 @@ int main(int argc, char *argv[]) {
}
}
char *shmname = root_atom_contents("I3_SHMLOG_PATH");
char *shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
if (shmname == NULL) {
/* Something failed. Lets invest a little effort to find out what it
* is. This is hugely helpful for users who want to debug i3 but are
@ -109,7 +109,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "FYI: The DISPLAY environment variable is set to \"%s\".\n", getenv("DISPLAY"));
exit(1);
}
if (root_atom_contents("I3_CONFIG_PATH") != NULL) {
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
if (!is_debug_build()) {
fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION);

View File

@ -368,23 +368,23 @@ int main(int argc, char *argv[]) {
printf("using format \"%s\"\n", format);
int screen;
conn = xcb_connect(NULL, &screen);
if (!conn || xcb_connection_has_error(conn))
die("Cannot open display\n");
if (socket_path == NULL)
socket_path = root_atom_contents("I3_SOCKET_PATH");
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
if (socket_path == NULL)
socket_path = "/tmp/i3-ipc.sock";
sockfd = ipc_connect(socket_path);
int screens;
conn = xcb_connect(NULL, &screens);
if (!conn || xcb_connection_has_error(conn))
die("Cannot open display\n");
/* Request the current InputFocus to restore when i3-input exits. */
focus_cookie = xcb_get_input_focus(conn);
root_screen = xcb_aux_get_screen(conn, screens);
root_screen = xcb_aux_get_screen(conn, screen);
root = root_screen->root;
symbols = xcb_key_symbols_alloc(conn);

View File

@ -188,7 +188,7 @@ int main(int argc, char *argv[]) {
}
if (socket_path == NULL)
socket_path = root_atom_contents("I3_SOCKET_PATH");
socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
/* Fall back to the default socket path */
if (socket_path == NULL)

View File

@ -22,6 +22,7 @@
#include <getopt.h>
#include <limits.h>
#include <fcntl.h>
#include <paths.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -95,15 +96,8 @@ static void start_application(const char *command) {
/* Child process */
setsid();
if (fork() == 0) {
/* Stores the path of the shell */
static const char *shell = NULL;
if (shell == NULL)
if ((shell = getenv("SHELL")) == NULL)
shell = "/bin/sh";
/* This is the child */
execl(shell, shell, "-c", command, (void*)NULL);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL);
/* not reached */
}
exit(0);
@ -165,8 +159,9 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
fclose(script);
char *link_path;
char *exe_path = get_exe_path(argv0);
sasprintf(&link_path, "%s.nagbar_cmd", script_path);
symlink(get_exe_path(argv0), link_path);
symlink(exe_path, link_path);
char *terminal_cmd;
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
@ -178,6 +173,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
free(link_path);
free(terminal_cmd);
free(script_path);
free(exe_path);
/* TODO: unset flag, re-render */
}
@ -198,8 +194,12 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
4 + 4, 4 + 4, rect.width - 4 - 4);
/* render close button */
const char *close_button_label = "X";
int line_width = 4;
int w = 20;
/* set width to the width of the label */
int w = predict_text_width(i3string_from_utf8(close_button_label));
/* account for left/right padding, which seems to be set to 8px (total) below */
w += 8;
int y = rect.width;
uint32_t values[3];
values[0] = color_button_background;
@ -221,7 +221,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
values[0] = 1;
set_font_colors(pixmap_gc, color_text, color_button_background);
draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
/* the x term here seems to set left/right padding */
draw_text_ascii(close_button_label, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
y -= w;
@ -230,8 +231,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
/* render custom buttons */
line_width = 1;
for (int c = 0; c < buttoncnt; c++) {
/* TODO: make w = text extents of the label */
w = 100;
/* set w to the width of the label */
w = predict_text_width(buttons[c].label);
/* account for left/right padding, which seems to be set to 12px (total) below */
w += 12;
y -= 30;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background });
close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
@ -252,6 +255,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
values[0] = color_text;
values[1] = color_button_background;
set_font_colors(pixmap_gc, color_text, color_button_background);
/* the x term seems to set left/right padding */
draw_text(buttons[c].label, pixmap, pixmap_gc,
y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);

View File

@ -84,6 +84,13 @@ bindsym Mod1+a focus parent
# focus the child container
#bindsym Mod1+d focus child
# move the currently focused window to the scratchpad
bindsym Mod1+Shift+minus move scratchpad
# Show the next scratchpad window or hide the focused scratchpad window.
# If there are multiple scratchpad windows, this command cycles through them.
bindsym Mod1+minus scratchpad show
# switch to workspace
bindsym Mod1+1 workspace 1
bindsym Mod1+2 workspace 2

View File

@ -23,7 +23,8 @@ typedef struct config_t {
position_t position;
int verbose;
struct xcb_color_strings_t colors;
int disable_ws;
bool disable_binding_mode_indicator;
bool disable_ws;
char *bar_id;
char *command;
char *fontname;

View File

@ -13,6 +13,7 @@
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
@ -22,6 +23,7 @@
#include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h>
#include <yajl/yajl_gen.h>
#include <paths.h>
#include "common.h"
@ -59,6 +61,58 @@ char *statusline_buffer = NULL;
int child_stdin;
/*
* Clears all blocks from the statusline structure in memory and frees their
* associated resources.
*/
static void clear_status_blocks() {
struct status_block *first;
while (!TAILQ_EMPTY(&statusline_head)) {
first = TAILQ_FIRST(&statusline_head);
I3STRING_FREE(first->full_text);
TAILQ_REMOVE(&statusline_head, first, blocks);
free(first);
}
}
/*
* Replaces the statusline in memory with an error message. Pass a format
* string and format parameters as you would in `printf'. The next time
* `draw_bars' is called, the error message text will be drawn on the bar in
* the space allocated for the statusline.
*/
/* forward function declaration is needed to add __attribute__ mechanism which
* helps the compiler understand we are defining a printf wrapper */
static void set_statusline_error(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
static void set_statusline_error(const char *format, ...) {
clear_status_blocks();
char *message;
va_list args;
va_start(args, format);
vasprintf(&message, format, args);
struct status_block *err_block = scalloc(sizeof(struct status_block));
err_block->full_text = i3string_from_utf8("Error: ");
err_block->name = "error";
err_block->color = "red";
err_block->no_separator = true;
struct status_block *message_block = scalloc(sizeof(struct status_block));
message_block->full_text = i3string_from_utf8(message);
message_block->name = "error_message";
message_block->color = "red";
message_block->no_separator = true;
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
FREE(message);
va_end(args);
}
/*
* Stop and free() the stdin- and sigchild-watchers
*
@ -240,6 +294,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
/* end of file, kill the watcher */
ELOG("stdin: received EOF\n");
cleanup();
set_statusline_error("Received EOF from statusline process");
draw_bars(false);
*ret_buffer_len = -1;
return NULL;
@ -279,8 +334,18 @@ static bool read_json_input(unsigned char *input, int length) {
#else
if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
#endif
fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
status, length, input);
char *message = (char *)yajl_get_error(parser, 0, input, length);
/* strip the newline yajl adds to the error message */
if (message[strlen(message) - 1] == '\n')
message[strlen(message) - 1] = '\0';
fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
status, message, length, input);
set_statusline_error("Could not parse JSON (%s)", message);
yajl_free_error(parser, (unsigned char*)message);
draw_bars(false);
} else if (parser_context.has_urgent) {
has_urgent = true;
}
@ -350,10 +415,23 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
*
*/
void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
int exit_status = WEXITSTATUS(watcher->rstatus);
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
child.pid,
watcher->rstatus);
exit_status);
/* this error is most likely caused by a user giving a nonexecutable or
* nonexistent file, so we will handle those cases separately. */
if (exit_status == 126)
set_statusline_error("status_command is not executable (exit %d)", exit_status);
else if (exit_status == 127)
set_statusline_error("status_command not found (exit %d)", exit_status);
else
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
cleanup();
draw_bars(false);
}
void child_write_output(void) {
@ -423,12 +501,8 @@ void start_child(char *command) {
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
static const char *shell = NULL;
if ((shell = getenv("SHELL")) == NULL)
shell = "/bin/sh";
execl(shell, shell, "-c", command, (char*) NULL);
setpgid(child.pid, 0);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL);
return;
default:
/* Parent-process. Reroute streams */
@ -511,8 +585,8 @@ void send_block_clicked(int button, const char *name, const char *instance, int
void kill_child_at_exit(void) {
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
kill(child.pid, child.cont_signal);
kill(child.pid, SIGTERM);
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
}
}
@ -524,8 +598,8 @@ void kill_child_at_exit(void) {
void kill_child(void) {
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
kill(child.pid, child.cont_signal);
kill(child.pid, SIGTERM);
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
int status;
waitpid(child.pid, &status, 0);
cleanup();
@ -539,7 +613,7 @@ void kill_child(void) {
void stop_child(void) {
if (child.stop_signal > 0 && !child.stopped) {
child.stopped = true;
kill(child.pid, child.stop_signal);
killpg(child.pid, child.stop_signal);
}
}
@ -550,6 +624,6 @@ void stop_child(void) {
void cont_child(void) {
if (child.cont_signal > 0 && child.stopped) {
child.stopped = false;
kill(child.pid, child.cont_signal);
killpg(child.pid, child.cont_signal);
}
}

View File

@ -193,6 +193,12 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
*
*/
static int config_boolean_cb(void *params_, int val) {
if (!strcmp(cur_key, "binding_mode_indicator")) {
DLOG("binding_mode_indicator = %d\n", val);
config.disable_binding_mode_indicator = !val;
return 1;
}
if (!strcmp(cur_key, "workspace_buttons")) {
DLOG("workspace_buttons = %d\n", val);
config.disable_ws = !val;

View File

@ -59,6 +59,9 @@ xcb_connection_t *conn;
/* The font we'll use */
static i3Font font;
/* Overall height of the bar (based on font size) */
int bar_height;
/* These are only relevant for XKB, which we only need for grabbing modifiers */
Display *xkb_dpy;
int xkb_event_base;
@ -240,9 +243,9 @@ void unhide_bars(void) {
values[0] = walk->rect.x;
if (config.position == POS_TOP)
values[1] = walk->rect.y;
else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
else values[1] = walk->rect.y + walk->rect.h - bar_height;
values[2] = walk->rect.w;
values[3] = font.height + 6;
values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
cookie = xcb_configure_window_checked(xcb_connection,
@ -430,8 +433,9 @@ void handle_button(xcb_button_press_event_t *event) {
}
/*
* Configures the x coordinate of all trayclients. To be called after adding a
* new tray client or removing an old one.
* Adjusts the size of the tray window and alignment of the tray clients by
* configuring their respective x coordinates. To be called when mapping or
* unmapping a tray client window.
*
*/
static void configure_trayclients(void) {
@ -607,7 +611,6 @@ static void handle_client_message(xcb_client_message_event_t* event) {
}
trayclient *tc = smalloc(sizeof(trayclient));
tc->win = client;
tc->mapped = map_it;
tc->xe_version = xe_version;
TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
@ -620,8 +623,69 @@ static void handle_client_message(xcb_client_message_event_t* event) {
}
/*
* Handles UnmapNotify events. These events happen when a tray window unmaps
* itself. We then update our data structure
* Handles DestroyNotify events by removing the tray client from the data
* structure. According to the XEmbed protocol, this is one way for a tray
* client to finish the protocol. After this event is received, there is no
* further interaction with the tray client.
*
* See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
*
*/
static void handle_destroy_notify(xcb_destroy_notify_event_t* event) {
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
i3_output *walk;
SLIST_FOREACH(walk, outputs, slist) {
if (!walk->active)
continue;
DLOG("checking output %s\n", walk->name);
trayclient *trayclient;
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
if (trayclient->win != event->window)
continue;
DLOG("Removing tray client with window ID %08x\n", event->window);
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
/* Trigger an update, we now have more space for the statusline */
configure_trayclients();
draw_bars(false);
return;
}
}
}
/*
* Handles MapNotify events. These events happen when a tray client shows its
* window. We respond by realigning the tray clients.
*
*/
static void handle_map_notify(xcb_map_notify_event_t* event) {
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
i3_output *walk;
SLIST_FOREACH(walk, outputs, slist) {
if (!walk->active)
continue;
DLOG("checking output %s\n", walk->name);
trayclient *trayclient;
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
if (trayclient->win != event->window)
continue;
DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
trayclient->mapped = true;
/* Trigger an update, we now have more space for the statusline */
configure_trayclients();
draw_bars(false);
return;
}
}
}
/*
* Handles UnmapNotify events. These events happen when a tray client hides its
* window. We respond by realigning the tray clients.
*
*/
static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
@ -637,8 +701,8 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
if (trayclient->win != event->window)
continue;
DLOG("Removing tray client with window ID %08x\n", event->window);
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
trayclient->mapped = false;
/* Trigger an update, we now have more space for the statusline */
configure_trayclients();
@ -705,15 +769,9 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
if (trayclient->mapped && !map_it) {
/* need to unmap the window */
xcb_unmap_window(xcb_connection, trayclient->win);
trayclient->mapped = map_it;
configure_trayclients();
draw_bars(false);
} else if (!trayclient->mapped && map_it) {
/* need to map the window */
xcb_map_window(xcb_connection, trayclient->win);
trayclient->mapped = map_it;
configure_trayclients();
draw_bars(false);
}
free(xembedr);
}
@ -795,11 +853,17 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
* example system tray widgets talk to us directly via client messages. */
handle_client_message((xcb_client_message_event_t*) event);
break;
case XCB_UNMAP_NOTIFY:
case XCB_DESTROY_NOTIFY:
/* UnmapNotifies are received when a tray window unmaps itself */
/* DestroyNotify signifies the end of the XEmbed protocol */
handle_destroy_notify((xcb_destroy_notify_event_t*) event);
break;
case XCB_UNMAP_NOTIFY:
/* UnmapNotify is received when a tray client hides its window. */
handle_unmap_notify((xcb_unmap_notify_event_t*) event);
break;
case XCB_MAP_NOTIFY:
handle_map_notify((xcb_map_notify_event_t*) event);
break;
case XCB_PROPERTY_NOTIFY:
/* PropertyNotify */
handle_property_notify((xcb_property_notify_event_t*) event);
@ -957,26 +1021,7 @@ char *init_xcb_early() {
/* Now we get the atoms and save them in a nice data structure */
get_atoms();
xcb_get_property_cookie_t path_cookie;
path_cookie = xcb_get_property_unchecked(xcb_connection,
0,
xcb_root,
atoms[I3_SOCKET_PATH],
XCB_GET_PROPERTY_TYPE_ANY,
0, PATH_MAX);
/* We check, if i3 set its socket-path */
xcb_get_property_reply_t *path_reply = xcb_get_property_reply(xcb_connection,
path_cookie,
NULL);
char *path = NULL;
if (path_reply) {
int len = xcb_get_property_value_length(path_reply);
if (len != 0) {
path = strndup(xcb_get_property_value(path_reply), len);
}
}
char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen);
if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
@ -1061,6 +1106,7 @@ void init_xcb_late(char *fontname) {
font = load_font(fontname, true);
set_font(&font);
DLOG("Calculated Font-height: %d\n", font.height);
bar_height = font.height + 6;
xcb_flush(xcb_connection);
@ -1334,7 +1380,7 @@ void realloc_sl_buffer(void) {
statusline_pm,
xcb_root,
MAX(root_screen->width_in_pixels, statusline_width),
root_screen->height_in_pixels);
bar_height);
uint32_t mask = XCB_GC_FOREGROUND;
uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
@ -1407,8 +1453,8 @@ void reconfig_windows(bool redraw_bars) {
root_screen->root_depth,
walk->bar,
xcb_root,
walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
walk->rect.w, font.height + 6,
walk->rect.x, walk->rect.y + walk->rect.h - bar_height,
walk->rect.w, bar_height,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
root_screen->root_visual,
@ -1421,7 +1467,7 @@ void reconfig_windows(bool redraw_bars) {
walk->buffer,
walk->bar,
walk->rect.w,
walk->rect.h);
bar_height);
/* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
xcb_void_cookie_t class_cookie;
@ -1482,12 +1528,12 @@ void reconfig_windows(bool redraw_bars) {
case POS_NONE:
break;
case POS_TOP:
strut_partial.top = font.height + 6;
strut_partial.top = bar_height;
strut_partial.top_start_x = walk->rect.x;
strut_partial.top_end_x = walk->rect.x + walk->rect.w;
break;
case POS_BOT:
strut_partial.bottom = font.height + 6;
strut_partial.bottom = bar_height;
strut_partial.bottom_start_x = walk->rect.x;
strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
break;
@ -1527,10 +1573,20 @@ void reconfig_windows(bool redraw_bars) {
exit(EXIT_FAILURE);
}
if (!tray_configured &&
(!config.tray_output ||
strcasecmp("none", config.tray_output) != 0)) {
init_tray();
const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name);
if (!tray_configured && strcasecmp(tray_output, "none") != 0) {
/* Configuration sanity check: ensure this i3bar instance handles the output on
* which the tray should appear (e.g. dont initialize a tray if tray_output ==
* VGA-1 but output == [HDMI-1]).
*/
i3_output *output;
SLIST_FOREACH(output, outputs, slist) {
if (strcasecmp(output->name, tray_output) == 0 ||
(strcasecmp(tray_output, "primary") == 0 && output->primary)) {
init_tray();
break;
}
}
tray_configured = true;
}
} else {
@ -1541,9 +1597,9 @@ void reconfig_windows(bool redraw_bars) {
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x;
values[1] = walk->rect.y + walk->rect.h - font.height - 6;
values[1] = walk->rect.y + walk->rect.h - bar_height;
values[2] = walk->rect.w;
values[3] = font.height + 6;
values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Destroying buffer for output %s\n", walk->name);
@ -1569,7 +1625,7 @@ void reconfig_windows(bool redraw_bars) {
walk->buffer,
walk->bar,
walk->rect.w,
walk->rect.h);
bar_height);
xcb_void_cookie_t map_cookie, umap_cookie;
if (redraw_bars) {
@ -1612,9 +1668,6 @@ void draw_bars(bool unhide) {
refresh_statusline();
static char *last_urgent_ws = NULL;
bool walks_away = true;
i3_output *outputs_walk;
SLIST_FOREACH(outputs_walk, outputs, slist) {
if (!outputs_walk->active) {
@ -1631,7 +1684,7 @@ void draw_bars(bool unhide) {
outputs_walk->bargc,
XCB_GC_FOREGROUND,
&color);
xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, bar_height };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
@ -1666,72 +1719,64 @@ void draw_bars(bool unhide) {
MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
}
if (config.disable_ws) {
continue;
if (!config.disable_ws) {
i3_ws *ws_walk;
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
DLOG("Drawing Button for WS %s at x = %d, len = %d\n",
i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
uint32_t fg_color = colors.inactive_ws_fg;
uint32_t bg_color = colors.inactive_ws_bg;
uint32_t border_color = colors.inactive_ws_border;
if (ws_walk->visible) {
if (!ws_walk->focused) {
fg_color = colors.active_ws_fg;
bg_color = colors.active_ws_bg;
border_color = colors.active_ws_border;
} else {
fg_color = colors.focus_ws_fg;
bg_color = colors.focus_ws_bg;
border_color = colors.focus_ws_border;
}
}
if (ws_walk->urgent) {
DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name));
fg_color = colors.urgent_ws_fg;
bg_color = colors.urgent_ws_bg;
border_color = colors.urgent_ws_border;
unhide = true;
}
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
uint32_t vals_border[] = { border_color, border_color };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals_border);
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,
1,
&rect_border);
uint32_t vals[] = { bg_color, bg_color };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals);
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, 3, ws_walk->name_width);
i += 10 + ws_walk->name_width + 1;
}
}
i3_ws *ws_walk;
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
uint32_t fg_color = colors.inactive_ws_fg;
uint32_t bg_color = colors.inactive_ws_bg;
uint32_t border_color = colors.inactive_ws_border;
if (ws_walk->visible) {
if (!ws_walk->focused) {
fg_color = colors.active_ws_fg;
bg_color = colors.active_ws_bg;
border_color = colors.active_ws_border;
} else {
fg_color = colors.focus_ws_fg;
bg_color = colors.focus_ws_bg;
border_color = colors.focus_ws_border;
if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), last_urgent_ws) == 0)
walks_away = false;
}
}
if (ws_walk->urgent) {
DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name));
fg_color = colors.urgent_ws_fg;
bg_color = colors.urgent_ws_bg;
border_color = colors.urgent_ws_border;
unhide = true;
if (!ws_walk->focused) {
FREE(last_urgent_ws);
last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
}
}
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
uint32_t vals_border[] = { border_color, border_color };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals_border);
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,
1,
&rect_border);
uint32_t vals[] = { bg_color, bg_color };
xcb_change_gc(xcb_connection,
outputs_walk->bargc,
mask,
vals);
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, 3, ws_walk->name_width);
i += 10 + ws_walk->name_width + 1;
}
if (binding.name) {
if (binding.name && !config.disable_binding_mode_indicator) {
uint32_t fg_color = colors.urgent_ws_fg;
uint32_t bg_color = colors.urgent_ws_bg;
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
@ -1770,13 +1815,11 @@ void draw_bars(bool unhide) {
}
/* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */
bool should_unhide = (config.hidden_state == S_SHOW || (unhide && config.hidden_state == S_HIDE));
bool should_hide = (config.hide_on_modifier == M_INVISIBLE);
if (mod_pressed || (should_unhide && !should_hide)) {
if (mod_pressed ||
config.hidden_state == S_SHOW ||
unhide) {
unhide_bars();
} else if (!mod_pressed && (walks_away || should_hide)) {
FREE(last_urgent_ws);
} else if (config.hide_on_modifier == M_HIDE) {
hide_bars();
}

View File

@ -267,6 +267,10 @@ struct Barconfig {
* zero. */
bool hide_workspace_buttons;
/** Hide mode button? Configuration option is 'binding_mode_indicator no'
* but we invert the bool for the same reason as hide_workspace_buttons.*/
bool hide_binding_mode_indicator;
/** Enable verbose mode? Useful for debugging purposes. */
bool verbose;

View File

@ -74,6 +74,7 @@ CFGFUN(bar_socket_path, const char *socket_path);
CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_finish);

View File

@ -134,14 +134,37 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
#endif
/**
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
* and the given callback will be called with the parameters specified (client,
* border on which the click originally was), the original rect of the client,
* the event and the new coordinates (x, y).
* This is the return value of a drag operation like drag_pointer.
*
* DRAGGING will indicate the drag action is still in progress and can be
* continued or resolved.
*
* DRAG_SUCCESS will indicate the intention of the drag action should be
* carried out.
*
* DRAG_REVERT will indicate an attempt should be made to restore the state of
* the involved windows to their condition before the drag.
*
* DRAG_ABORT will indicate that the intention of the drag action cannot be
* carried out (e.g. because the window has been unmapped).
*
*/
void drag_pointer(Con *con, const xcb_button_press_event_t *event,
typedef enum {
DRAGGING = 0,
DRAG_SUCCESS,
DRAG_REVERT,
DRAG_ABORT
} drag_result_t;
/**
* This function grabs your pointer and keyboard and lets you drag stuff around
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
* be received and the given callback will be called with the parameters
* specified (client, border on which the click originally was), the original
* rect of the client, the event and the new coordinates (x, y).
*
*/
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
xcb_window_t confine_to, border_t border, int cursor,
callback_t callback, const void *extra);

View File

@ -84,11 +84,14 @@ void errorlog(char *fmt, ...);
* Try to get the contents of the given atom (for example I3_SOCKET_PATH) from
* the X11 root window and return NULL if it doesnt work.
*
* If the provided XCB connection is NULL, a new connection will be
* established.
*
* The memory for the contents is dynamically allocated and has to be
* free()d by the caller.
*
*/
char *root_atom_contents(const char *atomname);
char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen);
/**
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
@ -369,7 +372,8 @@ char *get_process_filename(const char *prefix);
*
* The implementation follows http://stackoverflow.com/a/933996/712014
*
* Returned value must be freed by the caller.
*/
const char *get_exe_path(const char *argv0);
char *get_exe_path(const char *argv0);
#endif

View File

@ -10,6 +10,8 @@
#ifndef I3_RESIZE_H
#define I3_RESIZE_H
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction);
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
#endif

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)
*
* xcursor.c: libXcursor support for themed cursors.
*
@ -10,7 +10,7 @@
#ifndef I3_XCURSOR_CURSOR_H
#define I3_XCURSOR_CURSOR_H
#include <X11/Xlib.h>
#include <xcb/xcb_cursor.h>
enum xcursor_cursor_t {
XCURSOR_CURSOR_POINTER = 0,
@ -26,7 +26,7 @@ enum xcursor_cursor_t {
};
void xcursor_load_cursors(void);
Cursor xcursor_get_cursor(enum xcursor_cursor_t c);
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c);
int xcursor_get_xcb_cursor(enum xcursor_cursor_t c);
/**

View File

@ -30,6 +30,21 @@ static double pango_font_red;
static double pango_font_green;
static double pango_font_blue;
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
PangoLayout *layout;
PangoContext *context;
context = pango_cairo_create_context(cr);
const double dpi = (double)root_screen->height_in_pixels * 25.4 /
(double)root_screen->height_in_millimeters;
LOG("X11 root window dictates %f DPI\n", dpi);
pango_cairo_context_set_resolution(context, dpi);
layout = pango_layout_new(context);
g_object_unref(context);
return layout;
}
/*
* Loads a Pango font description into an i3Font structure. Returns true
* on success, false otherwise.
@ -56,7 +71,7 @@ static bool load_pango_font(i3Font *font, const char *desc) {
/* Create a dummy Pango layout to compute the font height */
cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
cairo_t *cr = cairo_create(surface);
PangoLayout *layout = pango_cairo_create_layout(cr);
PangoLayout *layout = create_layout_with_dpi(cr);
pango_layout_set_font_description(layout, font->specific.pango_desc);
/* Get the font height */
@ -85,17 +100,21 @@ static void draw_text_pango(const char *text, size_t text_len,
cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
root_visual_type, x + max_width, y + savedFont->height);
cairo_t *cr = cairo_create(surface);
PangoLayout *layout = pango_cairo_create_layout(cr);
PangoLayout *layout = create_layout_with_dpi(cr);
gint height;
pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
pango_layout_set_width(layout, max_width * PANGO_SCALE);
pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
pango_layout_set_text(layout, text, text_len);
/* Do the drawing */
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
cairo_move_to(cr, x, y);
pango_layout_set_text(layout, text, text_len);
pango_cairo_update_layout(cr, layout);
pango_layout_get_pixel_size(layout, NULL, &height);
cairo_move_to(cr, x, y - (height - savedFont->height));
pango_cairo_show_layout(cr, layout);
/* Free resources */
@ -113,7 +132,7 @@ static int predict_text_width_pango(const char *text, size_t text_len) {
/* root_visual_type is cached in load_pango_font */
cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
cairo_t *cr = cairo_create(surface);
PangoLayout *layout = pango_cairo_create_layout(cr);
PangoLayout *layout = create_layout_with_dpi(cr);
/* Get the font width */
gint width;

View File

@ -3,6 +3,7 @@
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <errno.h>
#include "libi3.h"
@ -11,10 +12,14 @@
*
* The implementation follows http://stackoverflow.com/a/933996/712014
*
* Returned value must be freed by the caller.
*/
const char *get_exe_path(const char *argv0) {
static char destpath[PATH_MAX];
char tmp[PATH_MAX];
char *get_exe_path(const char *argv0) {
size_t destpath_size = 1024;
size_t tmp_size = 1024;
char *destpath = smalloc(destpath_size);
char *tmp = smalloc(tmp_size);
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
/* Linux and Debian/kFreeBSD provide /proc/self/exe */
@ -25,30 +30,48 @@ const char *get_exe_path(const char *argv0) {
#endif
ssize_t linksize;
if ((linksize = readlink(exepath, destpath, sizeof(destpath) - 1)) != -1) {
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
if (linksize != -1) {
/* readlink() does not NULL-terminate strings, so we have to. */
destpath[linksize] = '\0';
free(tmp);
return destpath;
}
#endif
/* argv[0] is most likely a full path if it starts with a slash. */
if (argv0[0] == '/')
return argv0;
if (argv0[0] == '/') {
free(tmp);
free(destpath);
return sstrdup(argv0);
}
/* if argv[0] contains a /, prepend the working directory */
if (strchr(argv0, '/') != NULL &&
getcwd(tmp, sizeof(tmp)) != NULL) {
snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0);
return destpath;
if (strchr(argv0, '/') != NULL) {
char *retgcwd;
while ((retgcwd = getcwd(tmp, tmp_size)) == NULL && errno == ERANGE) {
tmp_size = tmp_size * 2;
tmp = srealloc(tmp, tmp_size);
}
if (retgcwd != NULL) {
free(destpath);
sasprintf(&destpath, "%s/%s", tmp, argv0);
free(tmp);
return destpath;
}
}
/* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */
char *path = getenv("PATH");
if (path == NULL) {
/* _CS_PATH is typically something like "/bin:/usr/bin" */
confstr(_CS_PATH, tmp, sizeof(tmp));
while (confstr(_CS_PATH, tmp, tmp_size) > tmp_size) {
tmp_size = tmp_size * 2;
tmp = srealloc(tmp, tmp_size);
}
sasprintf(&path, ":%s", tmp);
} else {
path = strdup(path);
@ -59,16 +82,20 @@ const char *get_exe_path(const char *argv0) {
if ((component = strtok(str, ":")) == NULL)
break;
str = NULL;
snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0);
free(destpath);
sasprintf(&destpath, "%s/%s", component, argv0);
/* Of course this is not 100% equivalent to actually exec()ing the
* binary, but meh. */
if (access(destpath, X_OK) == 0) {
free(path);
free(tmp);
return destpath;
}
}
free(destpath);
free(path);
free(tmp);
/* Last resort: maybe its in /usr/bin? */
return "/usr/bin/i3-nagbar";
return sstrdup("/usr/bin/i3-nagbar");
}

View File

@ -15,6 +15,7 @@
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
#include <err.h>
#include "libi3.h"
@ -35,6 +36,9 @@ char *get_process_filename(const char *prefix) {
struct stat buf;
if (stat(dir, &buf) != 0) {
if (mkdir(dir, 0700) == -1) {
warn("Could not mkdir(%s)", dir);
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
getenv("XDG_RUNTIME_DIR"));
perror("mkdir()");
return NULL;
}

View File

@ -9,6 +9,8 @@
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include <stdlib.h>
#include <math.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
@ -19,19 +21,23 @@
* Try to get the contents of the given atom (for example I3_SOCKET_PATH) from
* the X11 root window and return NULL if it doesnt work.
*
* If the provided XCB connection is NULL, a new connection will be
* established.
*
* The memory for the contents is dynamically allocated and has to be
* free()d by the caller.
*
*/
char *root_atom_contents(const char *atomname) {
xcb_connection_t *conn;
char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen) {
xcb_intern_atom_cookie_t atom_cookie;
xcb_intern_atom_reply_t *atom_reply;
int screen;
char *content;
size_t content_max_words = 256;
xcb_connection_t *conn = provided_conn;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
if (provided_conn == NULL &&
((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn)))
return NULL;
atom_cookie = xcb_intern_atom(conn, 0, strlen(atomname), atomname);
@ -46,21 +52,51 @@ char *root_atom_contents(const char *atomname) {
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t *prop_reply;
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
if (prop_reply == NULL) {
free(atom_reply);
return NULL;
}
if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) {
/* We received an incomplete value. Ask again but with a properly
* adjusted size. */
content_max_words += ceil(prop_reply->bytes_after / 4.0);
/* Repeat the request, with adjusted size */
free(prop_reply);
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
if (prop_reply == NULL) {
free(atom_reply);
return NULL;
}
}
if (xcb_get_property_value_length(prop_reply) == 0) {
free(atom_reply);
free(prop_reply);
return NULL;
}
if (prop_reply->type == XCB_ATOM_CARDINAL) {
/* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL
* we query is I3_PID, which is 32-bit. */
if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1)
if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1) {
free(atom_reply);
free(prop_reply);
return NULL;
}
} else {
if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
(char*)xcb_get_property_value(prop_reply)) == -1)
(char*)xcb_get_property_value(prop_reply)) == -1) {
free(atom_reply);
free(prop_reply);
return NULL;
}
}
xcb_disconnect(conn);
if (provided_conn == NULL)
xcb_disconnect(conn);
free(atom_reply);
free(prop_reply);
return content;
}

View File

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

View File

@ -1,7 +1,7 @@
i3-dump-log(1)
==============
Michael Stapelberg <michael+i3@stapelberg.de>
v4.1, December 2011
Michael Stapelberg <michael@i3wm.org>
v4.6, September 2013
== NAME
@ -9,7 +9,7 @@ i3-dump-log - dumps the i3 SHM log
== SYNOPSIS
i3-dump-log [-s <socketpath>]
i3-dump-log [-s <socketpath>] [-f]
== DESCRIPTION
@ -19,6 +19,9 @@ figuring out what is going on, without permanently logging to a file.
With i3-dump-log, you can dump the SHM log to stdout.
The -f flag works like tail -f, i.e. the process does not terminate after
dumping the log, but prints new lines as they appear.
== EXAMPLE
i3-dump-log | gzip -9 > /tmp/i3-log.gz

View File

@ -209,11 +209,11 @@ state RESIZE_TILING:
-> call cmd_resize($way, $direction, $resize_px, "10")
state RESIZE_TILING_OR:
'ppt'
->
resize_ppt = word
->
end
-> RESIZE_TILING_FINAL
state RESIZE_TILING_FINAL:
'ppt', end
-> call cmd_resize($way, $direction, $resize_px, $resize_ppt)
# rename workspace <name> to <name>

View File

@ -345,20 +345,21 @@ state BAR:
error ->
'#' -> BAR_IGNORE_LINE
'set' -> BAR_IGNORE_LINE
'i3bar_command' -> BAR_BAR_COMMAND
'status_command' -> BAR_STATUS_COMMAND
'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE
'hidden_state' -> BAR_HIDDEN_STATE
'id' -> BAR_ID
'modifier' -> BAR_MODIFIER
'position' -> BAR_POSITION
'output' -> BAR_OUTPUT
'tray_output' -> BAR_TRAY_OUTPUT
'font' -> BAR_FONT
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
'verbose' -> BAR_VERBOSE
'colors' -> BAR_COLORS_BRACE
'i3bar_command' -> BAR_BAR_COMMAND
'status_command' -> BAR_STATUS_COMMAND
'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE
'hidden_state' -> BAR_HIDDEN_STATE
'id' -> BAR_ID
'modifier' -> BAR_MODIFIER
'position' -> BAR_POSITION
'output' -> BAR_OUTPUT
'tray_output' -> BAR_TRAY_OUTPUT
'font' -> BAR_FONT
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
'verbose' -> BAR_VERBOSE
'colors' -> BAR_COLORS_BRACE
'}'
-> call cfg_bar_finish(); INITIAL
@ -411,6 +412,10 @@ state BAR_FONT:
font = string
-> call cfg_bar_font($font); BAR
state BAR_BINDING_MODE_INDICATOR:
value = word
-> call cfg_bar_binding_mode_indicator($value); BAR
state BAR_WORKSPACE_BUTTONS:
value = word
-> call cfg_bar_workspace_buttons($value); BAR

View File

@ -28,45 +28,43 @@ typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_
*/
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
DLOG("border = %d, con = %p\n", border, con);
char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
/* look for a parent container with the right orientation */
Con *first = NULL, *second = NULL;
Con *resize_con = con;
while (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
con_orientation(resize_con->parent) != orientation)
resize_con = resize_con->parent;
DLOG("resize_con = %p\n", resize_con);
if (resize_con->type != CT_WORKSPACE &&
resize_con->type != CT_FLOATING_CON &&
con_orientation(resize_con->parent) == orientation) {
first = resize_con;
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
if (second == TAILQ_END(&(first->nodes_head))) {
second = NULL;
}
else if (way == 'p') {
Con *tmp = first;
first = second;
second = tmp;
}
DLOG("first = %p, second = %p, resize_con = %p\n",
first, second, resize_con);
Con *second = NULL;
Con *first = con;
direction_t search_direction;
switch (border) {
case BORDER_LEFT:
search_direction = D_LEFT;
break;
case BORDER_RIGHT:
search_direction = D_RIGHT;
break;
case BORDER_TOP:
search_direction = D_UP;
break;
case BORDER_BOTTOM:
search_direction = D_DOWN;
break;
}
if (first == NULL || second == NULL) {
DLOG("Resize not possible\n");
bool res = resize_find_tiling_participants(&first, &second, search_direction);
if (!res) {
LOG("No second container in this direction found.\n");
return false;
}
assert(first != second);
assert(first->parent == second->parent);
/* The first container should always be in front of the second container */
if (search_direction == D_UP || search_direction == D_LEFT) {
Con *tmp = first;
first = second;
second = tmp;
}
/* We modify the X/Y position in the event so that the divider line is at
* the actual position of the border, not at the position of the click. */
const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
if (orientation == HORIZ)
event->root_x = second->rect.x;
else event->root_y = second->rect.y;

View File

@ -24,6 +24,14 @@
y(bool, success); \
y(map_close); \
} while (0)
#define yerror(message) do { \
y(map_open); \
ystr("success"); \
y(bool, false); \
ystr("error"); \
ystr(message); \
y(map_close); \
} while (0)
/** When the command did not include match criteria (!), we use the currently
* focused container. Do not confuse this case with a command which included
@ -69,6 +77,17 @@ static Output *get_output_from_string(Output *current_output, const char *output
return output;
}
/*
* Returns the output containing the given container.
*/
static Output *get_output_of_con(Con *con) {
Con *output_con = con_get_output(con);
Output *output = get_output_by_name(output_con->name);
assert(output != NULL);
return output;
}
/*
* Checks whether we switched to a new workspace and returns false in that case,
* signaling that further workspace switching should be done by the calling function
@ -441,12 +460,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
ws = workspace_back_and_forth_get();
if (ws == NULL) {
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("No workspace was previously active.");
y(map_close);
yerror("No workspace was previously active.");
return;
}
@ -535,13 +549,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
parsed_num < 0 ||
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
// TODO: better error message
ystr("Could not parse number");
y(map_close);
yerror("Could not parse number");
return;
}
@ -618,79 +627,50 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
LOG("tiling resize\n");
/* get the appropriate current container (skip stacked/tabbed cons) */
Con *other = NULL;
double percentage = 0;
while (current->parent->layout == L_STACKED ||
current->parent->layout == L_TABBED)
current = current->parent;
Con *second = NULL;
Con *first = current;
direction_t search_direction;
if (!strcmp(direction, "left"))
search_direction = D_LEFT;
else if (!strcmp(direction, "right"))
search_direction = D_RIGHT;
else if (!strcmp(direction, "up"))
search_direction = D_UP;
else
search_direction = D_DOWN;
/* Then further go up until we find one with the matching orientation. */
orientation_t search_orientation =
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
do {
if (con_orientation(current->parent) != search_orientation) {
current = current->parent;
continue;
}
/* get the default percentage */
int children = con_num_children(current->parent);
LOG("ins. %d children\n", children);
percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
orientation_t orientation = con_orientation(current->parent);
if ((orientation == HORIZ &&
(strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
(orientation == VERT &&
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
(orientation == HORIZ ? "horizontal" : "vertical"));
ysuccess(false);
return false;
}
if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
other = TAILQ_PREV(current, nodes_head, nodes);
} else {
other = TAILQ_NEXT(current, nodes);
}
if (other == TAILQ_END(workspaces)) {
LOG("No other container in this direction found, trying to look further up in the tree...\n");
current = current->parent;
continue;
}
break;
} while (current->type != CT_WORKSPACE &&
current->type != CT_FLOATING_CON);
if (other == NULL) {
LOG("No other container in this direction found, trying to look further up in the tree...\n");
bool res = resize_find_tiling_participants(&first, &second, search_direction);
if (!res) {
LOG("No second container in this direction found.\n");
ysuccess(false);
return false;
}
LOG("other->percent = %f\n", other->percent);
LOG("current->percent before = %f\n", current->percent);
if (current->percent == 0.0)
current->percent = percentage;
if (other->percent == 0.0)
other->percent = percentage;
double new_current_percent = current->percent + ((double)ppt / 100.0);
double new_other_percent = other->percent - ((double)ppt / 100.0);
LOG("new_current_percent = %f\n", new_current_percent);
LOG("new_other_percent = %f\n", new_other_percent);
/* get the default percentage */
int children = con_num_children(first->parent);
LOG("ins. %d children\n", children);
double percentage = 1.0 / children;
LOG("default percentage = %f\n", percentage);
/* resize */
LOG("second->percent = %f\n", second->percent);
LOG("first->percent before = %f\n", first->percent);
if (first->percent == 0.0)
first->percent = percentage;
if (second->percent == 0.0)
second->percent = percentage;
double new_first_percent = first->percent + ((double)ppt / 100.0);
double new_second_percent = second->percent - ((double)ppt / 100.0);
LOG("new_first_percent = %f\n", new_first_percent);
LOG("new_second_percent = %f\n", new_second_percent);
/* Ensure that the new percentages are positive and greater than
* 0.05 to have a reasonable minimum size. */
if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
current->percent += ((double)ppt / 100.0);
other->percent -= ((double)ppt / 100.0);
LOG("current->percent after = %f\n", current->percent);
LOG("other->percent after = %f\n", other->percent);
if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
first->percent += ((double)ppt / 100.0);
second->percent -= ((double)ppt / 100.0);
LOG("first->percent after = %f\n", first->percent);
LOG("second->percent after = %f\n", second->percent);
} else {
LOG("Not resizing, already at minimum size\n");
}
@ -939,13 +919,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
parsed_num < 0 ||
endptr == which) {
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
// TODO: better error message
ystr("Could not parse number");
y(map_close);
yerror("Could not parse number");
return;
}
@ -1085,7 +1060,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
// TODO: fix the handling of criteria
TAILQ_FOREACH(current, &owindows, owindows)
current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
current_output = get_output_of_con(current->con);
assert(current_output != NULL);
@ -1167,8 +1142,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) {
Output *current_output = get_output_containing(current->con->rect.x,
current->con->rect.y);
Output *current_output = get_output_of_con(current->con);
if (!current_output) {
ELOG("Cannot get current output. This is a bug in i3.\n");
ysuccess(false);
@ -1439,12 +1413,7 @@ void cmd_focus(I3_CMD) {
ELOG("You have to specify which window/container should be focused.\n");
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("You have to specify which window/container should be focused");
y(map_close);
yerror("You have to specify which window/container should be focused");
return;
}
@ -1521,7 +1490,7 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
HANDLE_EMPTY_MATCH;
TAILQ_FOREACH(current, &owindows, owindows) {
printf("matching: %p / %s\n", current->con, current->con->name);
DLOG("matching: %p / %s\n", current->con, current->con->name);
con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
}
@ -1713,7 +1682,7 @@ void cmd_focus_output(I3_CMD, char *name) {
Output *output;
TAILQ_FOREACH(current, &owindows, owindows)
current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
current_output = get_output_of_con(current->con);
assert(current_output != NULL);
output = get_output_from_string(current_output, name);
@ -1750,12 +1719,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
if (!con_is_floating(focused)) {
ELOG("Cannot change position. The window/container is not floating\n");
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("Cannot change position. The window/container is not floating.");
y(map_close);
yerror("Cannot change position. The window/container is not floating.");
return;
}
@ -1790,12 +1754,7 @@ void cmd_move_window_to_center(I3_CMD, char *method) {
if (!con_is_floating(focused)) {
ELOG("Cannot change position. The window/container is not floating\n");
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
ystr("Cannot change position. The window/container is not floating.");
y(map_close);
yerror("Cannot change position. The window/container is not floating.");
return;
}
@ -1890,13 +1849,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
if (!workspace) {
// TODO: we should include the old workspace name here and use yajl for
// generating the reply.
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
// TODO: better error message
ystr("Old workspace not found");
y(map_close);
yerror("Old workspace not found");
return;
}
@ -1908,13 +1862,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
if (check_dest != NULL) {
// TODO: we should include the new workspace name here and use yajl for
// generating the reply.
y(map_open);
ystr("success");
y(bool, false);
ystr("error");
// TODO: better error message
ystr("New workspace already exists");
y(map_close);
yerror("New workspace already exists");
return;
}
@ -1950,7 +1899,7 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
*
*/
bool cmd_bar_mode(char *bar_mode, char *bar_id) {
int mode;
int mode = M_DOCK;
bool toggle = false;
if (strcmp(bar_mode, "dock") == 0)
mode = M_DOCK;
@ -1995,7 +1944,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) {
*
*/
bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) {
int hidden_state;
int hidden_state = S_SHOW;
bool toggle = false;
if (strcmp(bar_hidden_state, "hide") == 0)
hidden_state = S_HIDE;

View File

@ -569,8 +569,9 @@ void con_fix_percent(Con *con) {
}
/*
* Toggles fullscreen mode for the given container. Fullscreen mode will not be
* entered when there already is a fullscreen container on this workspace.
* Toggles fullscreen mode for the given container. If there already is a
* fullscreen container on this workspace, fullscreen will be disabled and then
* enabled for the container the user wants to have in fullscreen mode.
*
*/
void con_toggle_fullscreen(Con *con, int fullscreen_mode) {

View File

@ -14,16 +14,6 @@
#include "all.h"
// Macros to make the YAJL API a bit easier to use.
#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
#define ysuccess(success) do { \
y(map_open); \
ystr("success"); \
y(bool, success); \
y(map_close); \
} while (0)
/*******************************************************************************
* Criteria functions.
******************************************************************************/
@ -550,6 +540,10 @@ CFGFUN(bar_status_command, const char *command) {
current_bar.status_command = sstrdup(command);
}
CFGFUN(bar_binding_mode_indicator, const char *value) {
current_bar.hide_binding_mode_indicator = !eval_boolstr(value);
}
CFGFUN(bar_workspace_buttons, const char *value) {
current_bar.hide_workspace_buttons = !eval_boolstr(value);
}

View File

@ -68,11 +68,11 @@ static yajl_callbacks version_callbacks = {
*
*/
void display_running_version(void) {
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
if (socket_path == NULL)
exit(EXIT_SUCCESS);
char *pid_from_atom = root_atom_contents("I3_PID");
char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
if (pid_from_atom == NULL) {
/* If I3_PID is not set, the running version is older than 4.2-200. */
printf("\nRunning version: < 4.2-200\n");
@ -128,13 +128,18 @@ void display_running_version(void) {
printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
#ifdef __linux__
char exepath[PATH_MAX],
destpath[PATH_MAX];
size_t destpath_size = 1024;
ssize_t linksize;
char *exepath;
char *destpath = smalloc(destpath_size);
snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid());
sasprintf(&exepath, "/proc/%d/exe", getpid());
if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
if (linksize == -1)
err(EXIT_FAILURE, "readlink(%s)", exepath);
/* readlink() does not NULL-terminate strings, so we have to. */
@ -143,9 +148,14 @@ void display_running_version(void) {
printf("\n");
printf("The i3 binary you just called: %s\n", destpath);
snprintf(exepath, sizeof(exepath), "/proc/%s/exe", pid_from_atom);
free(exepath);
sasprintf(&exepath, "/proc/%s/exe", pid_from_atom);
if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
destpath_size = destpath_size * 2;
destpath = srealloc(destpath, destpath_size);
}
if (linksize == -1)
err(EXIT_FAILURE, "readlink(%s)", exepath);
/* readlink() does not NULL-terminate strings, so we have to. */
@ -159,7 +169,8 @@ void display_running_version(void) {
/* Since readlink() might put a "(deleted)" somewhere in the buffer and
* stripping that out seems hackish and ugly, we read the processs argv[0]
* instead. */
snprintf(exepath, sizeof(exepath), "/proc/%s/cmdline", pid_from_atom);
free(exepath);
sasprintf(&exepath, "/proc/%s/cmdline", pid_from_atom);
int fd;
if ((fd = open(exepath, O_RDONLY)) == -1)
@ -169,6 +180,9 @@ void display_running_version(void) {
close(fd);
printf("The i3 binary you are running: %s\n", destpath);
free(exepath);
free(destpath);
#endif
yajl_free(handle);

View File

@ -441,8 +441,15 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
* after the user releases the mouse button */
tree_render();
/* Store the initial rect in case of user revert/cancel */
Rect initial_rect = con->rect;
/* Drag the window */
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
/* If the user cancelled, undo the changes. */
if (drag_result == DRAG_REVERT)
floating_reposition(con, initial_rect);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
@ -546,7 +553,14 @@ 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);
/* get the initial rect in case of revert/cancel */
Rect initial_rect = con->rect;
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
/* If the user cancels, undo the resize */
if (drag_result == DRAG_REVERT)
floating_reposition(con, initial_rect);
/* If this is a scratchpad window, don't auto center it from now on. */
if (con->scratchpad_state == SCRATCHPAD_FRESH)
@ -554,14 +568,14 @@ void floating_resize_window(Con *con, const bool proportional,
}
/*
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
* and the given callback will be called with the parameters specified (client,
* border on which the click originally was), the original rect of the client,
* the event and the new coordinates (x, y).
* This function grabs your pointer and keyboard and lets you drag stuff around
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
* be received and the given callback will be called with the parameters
* specified (client, border on which the click originally was), the original
* rect of the client, the event and the new coordinates (x, y).
*
*/
void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
confine_to, border_t border, int cursor, callback_t callback, const void *extra)
{
uint32_t new_x, new_y;
@ -569,7 +583,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
if (con != NULL)
memcpy(&old_rect, &(con->rect), sizeof(Rect));
Cursor xcursor = (cursor && xcursor_supported) ?
xcb_cursor_t xcursor = (cursor && xcursor_supported) ?
xcursor_get_cursor(cursor) : XCB_NONE;
/* Grab the pointer */
@ -587,18 +601,39 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
ELOG("Could not grab pointer\n");
return;
return DRAG_ABORT;
}
free(reply);
/* Grab the keyboard */
xcb_grab_keyboard_cookie_t keyb_cookie;
xcb_grab_keyboard_reply_t *keyb_reply;
keyb_cookie = xcb_grab_keyboard(conn,
false, /* get all keyboard events */
root, /* grab the root window */
XCB_CURRENT_TIME,
XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
XCB_GRAB_MODE_ASYNC /* keyboard mode */
);
if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
ELOG("Could not grab keyboard\n");
return DRAG_ABORT;
}
free(keyb_reply);
/* Go into our own event loop */
xcb_flush(conn);
xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
bool loop_done = false;
Con *inside_con = NULL;
drag_result_t drag_result = DRAGGING;
/* Ive always wanted to have my own eventhandler… */
while (!loop_done && (inside_event = xcb_wait_for_event(conn))) {
while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) {
/* We now handle all events we can get using xcb_poll_for_event */
do {
/* skip x11 errors */
@ -611,7 +646,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
switch (type) {
case XCB_BUTTON_RELEASE:
loop_done = true;
drag_result = DRAG_SUCCESS;
break;
case XCB_MOTION_NOTIFY:
@ -621,11 +656,26 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
break;
case XCB_UNMAP_NOTIFY:
case XCB_KEY_PRESS:
case XCB_KEY_RELEASE:
DLOG("Unmap-notify, aborting\n");
inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window);
if (inside_con != NULL) {
DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con);
if (con_get_workspace(inside_con) == con_get_workspace(focused)) {
DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
drag_result = DRAG_ABORT;
}
}
handle_event(type, inside_event);
break;
case XCB_KEY_PRESS:
/* Cancel the drag if a key was pressed */
DLOG("A key was pressed during drag, reverting changes.");
drag_result = DRAG_REVERT;
handle_event(type, inside_event);
loop_done = true;
break;
default:
@ -638,7 +688,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
free(inside_event);
} while ((inside_event = xcb_poll_for_event(conn)) != NULL);
if (last_motion_notify == NULL || loop_done)
if (last_motion_notify == NULL || drag_result != DRAGGING)
continue;
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
@ -648,8 +698,12 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
FREE(last_motion_notify);
}
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
xcb_flush(conn);
return drag_result;
}
/*

View File

@ -54,12 +54,12 @@ src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stam
i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
echo "[i3] Generating command parser"
(cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
(cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
touch $@
i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec
echo "[i3] Generating config parser"
(cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
(cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
touch $@
i3: libi3.a $(i3_OBJECTS)

View File

@ -686,6 +686,9 @@ IPC_HANDLER(get_bar_config) {
ystr("workspace_buttons");
y(bool, !config->hide_workspace_buttons);
ystr("binding_mode_indicator");
y(bool, !config->hide_binding_mode_indicator);
ystr("verbose");
y(bool, config->verbose);

View File

@ -129,11 +129,16 @@ void open_logbuffer(void) {
return;
}
#if defined(__APPLE__)
if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
#else
int ret;
if ((ret = posix_fallocate(logbuffer_shm, 0, logbuffer_size)) != 0) {
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret));
#endif
close(logbuffer_shm);
shm_unlink(shmlogname);
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret));
return;
}

View File

@ -352,7 +352,7 @@ int main(int argc, char *argv[]) {
break;
} else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 ||
strcmp(long_options[option_index].name, "get_socketpath") == 0) {
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
if (socket_path) {
printf("%s\n", socket_path);
exit(EXIT_SUCCESS);
@ -442,7 +442,7 @@ int main(int argc, char *argv[]) {
optind++;
}
DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload));
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
if (!socket_path) {
ELOG("Could not get i3 IPC socket path\n");
return 1;
@ -488,18 +488,25 @@ int main(int argc, char *argv[]) {
/* The following code is helpful, but not required. We thus dont pay
* much attention to error handling, non-linux or other edge cases. */
char cwd[PATH_MAX];
LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n");
if (getcwd(cwd, sizeof(cwd)) != NULL)
size_t cwd_size = 1024;
char *cwd = smalloc(cwd_size);
char *cwd_ret;
while ((cwd_ret = getcwd(cwd, cwd_size)) == NULL && errno == ERANGE) {
cwd_size = cwd_size * 2;
cwd = srealloc(cwd, cwd_size);
}
if (cwd_ret != NULL)
LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd);
int patternfd;
if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) {
memset(cwd, '\0', sizeof(cwd));
if (read(patternfd, cwd, sizeof(cwd)) > 0)
memset(cwd, '\0', cwd_size);
if (read(patternfd, cwd, cwd_size) > 0)
/* a trailing newline is included in cwd */
LOG("CORE DUMPS: Your core_pattern is: %s", cwd);
close(patternfd);
}
free(cwd);
}
LOG("i3 " I3_VERSION " starting\n");
@ -794,6 +801,27 @@ int main(int argc, char *argv[]) {
}
xcb_ungrab_server(conn);
if (autostart) {
LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
uint16_t width = root->width_in_pixels;
uint16_t height = root->height_in_pixels;
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
xcb_create_gc(conn, gc, root->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
(uint32_t[]){ XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS });
xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
xcb_change_window_attributes_checked(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){ pixmap });
xcb_flush(conn);
xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap);
}
struct sigaction action;
action.sa_sigaction = handle_signal;

View File

@ -279,11 +279,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) {
DLOG("Assignment matches (%p)\n", match);
if (assignment->type == A_TO_WORKSPACE) {
nc = con_descend_tiling_focused(workspace_get(assignment->dest.workspace, NULL));
DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name);
Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
nc = con_descend_tiling_focused(assigned_ws);
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
if (nc->type == CT_WORKSPACE)
nc = tree_open_con(nc, cwindow);
else nc = tree_open_con(nc->parent, cwindow);
else
nc = tree_open_con(nc->parent, cwindow);
/* set the urgency hint on the window if the workspace is not visible */
if (!workspace_is_visible(assigned_ws))
urgency_hint = true;
}
/* TODO: handle assignments with type == A_TO_OUTPUT */
} else if (startup_ws) {
@ -322,11 +328,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
x_set_name(nc, name);
free(name);
/* handle fullscreen containers */
Con *ws = con_get_workspace(nc);
Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
if (fs == NULL)
fs = con_get_fullscreen_con(croot, CF_GLOBAL);
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
fs = NULL;
con_toggle_fullscreen(nc, CF_OUTPUT);
}
FREE(state_reply);
if (fs == NULL) {
DLOG("Not in fullscreen mode, focusing\n");
if (!cwindow->dock) {
@ -430,12 +445,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
xcb_flush(conn);
reply = xcb_get_property_reply(conn, state_cookie, NULL);
if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN))
con_toggle_fullscreen(nc, CF_OUTPUT);
FREE(reply);
/* Put the client inside the save set. Upon termination (whether killed or
* normal exit does not matter) of the window manager, these clients will
* be correctly reparented to their most closest living ancestor (=

View File

@ -65,11 +65,12 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
}
/*
* This function detaches 'con' from its parent and inserts it at the given
* workspace.
* This function detaches 'con' from its parent and puts it in the given
* workspace. Position is determined by the direction of movement into the
* workspace container.
*
*/
static void attach_to_workspace(Con *con, Con *ws) {
static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
con_detach(con);
con_fix_percent(con->parent);
@ -77,8 +78,13 @@ static void attach_to_workspace(Con *con, Con *ws) {
con->parent = ws;
TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
if (direction == D_RIGHT || direction == D_DOWN) {
TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused);
} else {
TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
}
/* Pretend the con was just opened with regards to size percent values.
* Since the con is moved to a completely different con, the old value
@ -87,6 +93,32 @@ static void attach_to_workspace(Con *con, Con *ws) {
con_fix_percent(ws);
}
/*
* Moves the given container to the closest output in the given direction if
* such an output exists.
*
*/
static void move_to_output_directed(Con *con, direction_t direction) {
Con *current_output_con = con_get_output(con);
Output *current_output = get_output_by_name(current_output_con->name);
Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
if (!output) {
DLOG("No output in this direction found. Not moving.\n");
return;
}
Con *ws = NULL;
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
if (!ws) {
DLOG("No workspace on output in this direction found. Not moving.\n");
return;
}
attach_to_workspace(con, ws, direction);
}
/*
* Moves the current container in the given direction (D_LEFT, D_RIGHT,
* D_UP, D_DOWN).
@ -103,8 +135,9 @@ void tree_move(int direction) {
}
if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
DLOG("This is the only con on this workspace, not doing anything\n");
return;
/* This is the only con on this workspace */
move_to_output_directed(con, direction);
goto end;
}
orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
@ -124,7 +157,7 @@ void tree_move(int direction) {
if (con_inside_floating(con)) {
/* 'con' should be moved out of a floating container */
DLOG("Inside floating, moving to workspace\n");
attach_to_workspace(con, con_get_workspace(con));
attach_to_workspace(con, con_get_workspace(con), direction);
goto end;
}
DLOG("Force-changing orientation\n");
@ -154,12 +187,15 @@ void tree_move(int direction) {
return;
}
/* If there was no con with which we could swap the current one, search
* again, but starting one level higher. If we are on the workspace
* level, dont do that. The result would be a force change of
* workspace orientation, which is not necessary. */
if (con->parent == con_get_workspace(con))
return;
if (con->parent == con_get_workspace(con)) {
/* If we couldn't find a place to move it on this workspace,
* try to move it to a workspace on a different output */
move_to_output_directed(con, direction);
goto end;
}
/* If there was no con with which we could swap the current one,
* search again, but starting one level higher. */
same_orientation = con_parent_with_orientation(con->parent, o);
}
} while (same_orientation == NULL);

View File

@ -51,6 +51,54 @@ DRAGGING_CB(resize_callback) {
xcb_flush(conn);
}
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) {
DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
Con *first = *current;
Con *second = NULL;
if (first == NULL) {
DLOG("Current container is NULL, aborting.\n");
return false;
}
/* Go up in the tree and search for a container to resize */
const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT);
const bool dir_backwards = (direction == D_UP || direction == D_LEFT);
while (first->type != CT_WORKSPACE &&
first->type != CT_FLOATING_CON &&
second == NULL) {
/* get the appropriate first container with the matching
* orientation (skip stacked/tabbed cons) */
if ((con_orientation(first->parent) != search_orientation) ||
(first->parent->layout == L_STACKED) ||
(first->parent->layout == L_TABBED)) {
first = first->parent;
continue;
}
/* get the counterpart for this resizement */
if (dir_backwards) {
second = TAILQ_PREV(first, nodes_head, nodes);
} else {
second = TAILQ_NEXT(first, nodes);
}
if (second == NULL) {
DLOG("No second container in this direction found, trying to look further up in the tree...\n");
first = first->parent;
}
}
DLOG("Found participants: first=%p and second=%p.", first, second);
*current = first;
*other = second;
if (first == NULL || second == NULL) {
DLOG("Could not find two participants for this resize request.\n");
return false;
}
return true;
}
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
DLOG("resize handler\n");
@ -106,12 +154,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
const struct callback_params params = { orientation, output, helpwin, &new_position };
drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin);
xcb_flush(conn);
/* User cancelled the drag so no action should be taken. */
if (drag_result == DRAG_REVERT)
return 0;
int pixels;
if (orientation == HORIZ)
pixels = (new_position - event->root_x);

View File

@ -66,7 +66,13 @@ void scratchpad_move(Con *con) {
* adjusted in size according to what the user specifies. */
if (con->scratchpad_state == SCRATCHPAD_NONE) {
DLOG("This window was never used as a scratchpad before.\n");
con->scratchpad_state = SCRATCHPAD_FRESH;
if (con == maybe_floating_con) {
DLOG("It was in floating mode before, set scratchpad state to changed.\n");
con->scratchpad_state = SCRATCHPAD_CHANGED;
} else {
DLOG("It was in tiling mode before, set scratchpad state to fresh.\n");
con->scratchpad_state = SCRATCHPAD_FRESH;
}
}
}

View File

@ -17,6 +17,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <paths.h>
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
@ -191,15 +192,7 @@ void start_application(const char *command, bool no_startup_id) {
if (!no_startup_id)
sn_launcher_context_setup_child_process(context);
/* Stores the path of the shell */
static const char *shell = NULL;
if (shell == NULL)
if ((shell = getenv("SHELL")) == NULL)
shell = "/bin/sh";
/* This is the child */
execl(shell, shell, "-c", command, (void*)NULL);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL);
/* not reached */
}
_exit(0);

View File

@ -359,15 +359,24 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
*/
void tree_close_con(kill_window_t kill_window) {
assert(focused != NULL);
if (focused->type == CT_WORKSPACE) {
LOG("Cannot close workspace\n");
return;
}
/* There *should* be no possibility to focus outputs / root container */
assert(focused->type != CT_OUTPUT);
assert(focused->type != CT_ROOT);
if (focused->type == CT_WORKSPACE) {
DLOG("Workspaces cannot be close, closing all children instead\n");
Con *child, *nextchild;
for (child = TAILQ_FIRST(&(focused->focus_head)); child; ) {
nextchild = TAILQ_NEXT(child, focused);
DLOG("killing child=%p\n", child);
tree_close(child, kill_window, false, false);
child = nextchild;
}
return;
}
/* Kill con */
tree_close(focused, kill_window, false, false);
}

View File

@ -197,17 +197,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
while (exists) {
c++;
FREE(ws->name);
sasprintf(&(ws->name), "%d", c);
ws->num = c;
current = NULL;
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
GREP_FIRST(current, output_get_content(out), child->num == ws->num);
exists = (current != NULL);
DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
DLOG("result for ws %d: exists = %d\n", c, exists);
}
ws->num = c;
sasprintf(&(ws->name), "%d", c);
}
con_attach(ws, content, false);

View File

@ -4,20 +4,20 @@
* 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)
*
* xcursor.c: libXcursor support for themed cursors.
* xcursor.c: xcursor support for themed cursors.
*
*/
#include <assert.h>
#include <X11/Xcursor/Xcursor.h>
#include <X11/cursorfont.h>
#include <xcb/xcb_cursor.h>
#include "i3.h"
#include "xcb.h"
#include "xcursor.h"
static Cursor cursors[XCURSOR_CURSOR_MAX];
static xcb_cursor_context_t *ctx;
static xcb_cursor_t cursors[XCURSOR_CURSOR_MAX];
static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
XCB_CURSOR_LEFT_PTR,
@ -26,23 +26,26 @@ static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
XCB_CURSOR_WATCH
};
static Cursor load_cursor(const char *name) {
Cursor c = XcursorLibraryLoadCursor(xlibdpy, name);
if (c == None)
xcursor_supported = false;
return c;
}
void xcursor_load_cursors(void) {
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
cursors[XCURSOR_CURSOR_MOVE] = load_cursor("fleur");
cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER] = load_cursor("top_left_corner");
cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER] = load_cursor("top_right_corner");
cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER] = load_cursor("bottom_left_corner");
cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner");
if (xcb_cursor_context_new(conn, root_screen, &ctx) < 0) {
ELOG("xcursor support unavailable\n");
xcursor_supported = false;
return;
}
#define LOAD_CURSOR(constant, name) \
do { \
cursors[constant] = xcb_cursor_load_cursor(ctx, name); \
} while (0)
LOAD_CURSOR(XCURSOR_CURSOR_POINTER, "left_ptr");
LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_HORIZONTAL, "sb_h_double_arrow");
LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_VERTICAL, "sb_v_double_arrow");
LOAD_CURSOR(XCURSOR_CURSOR_WATCH, "watch");
LOAD_CURSOR(XCURSOR_CURSOR_MOVE, "fleur");
LOAD_CURSOR(XCURSOR_CURSOR_TOP_LEFT_CORNER, "top_left_corner");
LOAD_CURSOR(XCURSOR_CURSOR_TOP_RIGHT_CORNER, "top_right_corner");
LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_LEFT_CORNER, "bottom_left_corner");
LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER, "bottom_right_corner");
#undef LOAD_CURSOR
}
/*
@ -51,19 +54,13 @@ void xcursor_load_cursors(void) {
* This function is called when i3 is initialized, because with some login
* managers, the root window will not have a cursor otherwise.
*
* We have a separate xcursor function to use the same X11 connection as the
* xcursor_load_cursors() function. If we mix the Xlib and the XCB connection,
* races might occur (even though we flush the Xlib connection).
*
*/
void xcursor_set_root_cursor(int cursor_id) {
XSetWindowAttributes attributes;
attributes.cursor = xcursor_get_cursor(cursor_id);
XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
XFlush(xlibdpy);
xcb_change_window_attributes(conn, root, XCB_CW_CURSOR,
(uint32_t[]){ xcursor_get_cursor(cursor_id) });
}
Cursor xcursor_get_cursor(enum xcursor_cursor_t c) {
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) {
assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
return cursors[c];
}

View File

@ -8,3 +8,4 @@ inc
META.yml
i3-cfg-for-*
-
Xdummy.so

View File

@ -8,8 +8,8 @@ WriteMakefile(
MIN_PERL_VERSION => '5.010000', # 5.10.0
PREREQ_PM => {
'AnyEvent' => 0,
'AnyEvent::I3' => '0.14',
'X11::XCB' => '0.03',
'AnyEvent::I3' => '0.15',
'X11::XCB' => '0.09',
'Inline' => 0,
'ExtUtils::PkgConfig' => 0,
'Test::More' => '0.94',

View File

@ -108,7 +108,7 @@ $outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime());
$outdir .= `git describe --tags`;
chomp($outdir);
mkdir($outdir) or die "Could not create $outdir";
unlink("latest") if -e "latest";
unlink("latest") if -l "latest";
symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
@ -143,7 +143,7 @@ my $timingsjson = StartXDummy::slurp('.last_run_timings.json');
# Run 000-load-deps.t first to bail out early when dependencies are missing.
my $loadtest = "t/000-load-deps.t";
if ($loadtest ~~ @testfiles) {
if ((scalar grep { $_ eq $loadtest } @testfiles) > 0) {
@testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles);
}

View File

@ -113,7 +113,7 @@ sub start_xdummy {
# actual system X configuration.
my $socket = fork_xserver($keep_xdummy_output, $displaynum,
'./Xdummy', ":$displaynum", '-config', '/dev/null',
'-nolisten', 'tcp');
'-configdir', '/dev/null', '-nolisten', 'tcp');
push(@displays, ":$displaynum");
push(@sockets_waiting, $socket);
$displaynum++;

View File

@ -155,6 +155,9 @@ __
warnings->import;
$x ||= i3test::X11->new;
# set the pointer to a predictable position in case a previous test has
# disturbed it
$x->root->warp_pointer(0, 0);
$cv->recv if $i3_autostart;
@_ = ($class);
@ -406,7 +409,7 @@ C<fresh_workspace> which directly switches to an unused workspace.
sub get_unused_workspace {
my @names = get_workspace_names();
my $tmp;
do { $tmp = tmpnam() } while ($tmp ~~ @names);
do { $tmp = tmpnam() } while ((scalar grep { $_ eq $tmp } @names) > 0);
$tmp
}
@ -626,7 +629,7 @@ Returns true if C<$workspace> is the name of an existing workspace.
=cut
sub workspace_exists {
my ($name) = @_;
($name ~~ @{get_workspace_names()})
(scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
}
=head2 focused_ws

View File

@ -15,6 +15,16 @@ use File::Basename qw(basename);
use Getopt::Long;
use v5.10;
my $usage = <<'EOF';
Script to create a new testcase from a template.
# Create (and edit) a new test for moving floating windows
./new-test floating move
# Create (and edit) a multi-monitor test for moving workspaces
./new-test -m move workspaces
EOF
my $multi_monitor;
my $result = GetOptions(
@ -24,6 +34,11 @@ my $result = GetOptions(
my $testname = join(' ', @ARGV);
$testname =~ s/ /-/g;
unless (length $testname) {
say $usage;
exit(0);
}
my $header = <<'EOF';
#!perl
# vim:ts=4:sw=4:expandtab
@ -68,10 +83,6 @@ if ($multi_monitor) {
print $fh <<'EOF';
use i3test i3_autostart => 0;
# 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

View File

@ -214,4 +214,25 @@ sync_with_i3;
# Verify that $swindow was the one that initially remained fullscreen.
is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
################################################################################
# Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
# existing container on the workspace and fullscreens the newly opened window.
################################################################################
$tmp = fresh_workspace;
$window = open_window();
cmd "fullscreen";
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
is($x->input_focus, $window->id, 'fullscreen window focused');
$swindow = open_window({
fullscreen => 1
});
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
is($x->input_focus, $swindow->id, 'fullscreen window focused');
done_testing;

View File

@ -24,8 +24,11 @@ my $_NET_WM_STATE_TOGGLE = 2;
sub set_urgency {
my ($win, $urgent_flag, $type) = @_;
if ($type == 1) {
# Because X11::XCB does not keep track of clearing the urgency hint
# when receiving focus, we just delete it in all cases and then re-set
# it if appropriate.
$win->delete_hint('urgency');
$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

View File

@ -17,7 +17,6 @@
# Tests all kinds of matching methods
#
use i3test;
use X11::XCB qw(PROP_MODE_REPLACE);
my $tmp = fresh_workspace;
@ -61,39 +60,10 @@ is_num_children($tmp, 0, 'window killed');
$tmp = fresh_workspace;
# 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 $left = open_special(name => 'left');
my $left = open_window(wm_class => 'special', name => 'left');
ok($left->mapped, 'left window mapped');
my $right = open_special(name => 'right');
my $right = open_window(wm_class => 'special', name => 'right');
ok($right->mapped, 'right window mapped');
# two windows should be here
@ -111,7 +81,7 @@ is_num_children($tmp, 1, 'one window still there');
$tmp = fresh_workspace;
$left = open_special(name => 'left', wm_class => 'special7');
$left = open_window(name => 'left', wm_class => 'special7');
ok($left->mapped, 'left window mapped');
is_num_children($tmp, 1, 'window opened');
@ -125,7 +95,7 @@ is_num_children($tmp, 0, 'window killed');
$tmp = fresh_workspace;
$left = open_special(name => 'ä 3', wm_class => 'special7');
$left = open_window(name => 'ä 3', wm_class => 'special7');
ok($left->mapped, 'left window mapped');
is_num_children($tmp, 1, 'window opened');

View File

@ -119,6 +119,55 @@ sync_with_i3;
is(get_focused($tmp), $middle, 'middle container focused');
##############################################################
# check if the workspace container can be closed
##############################################################
$tmp = fresh_workspace;
my $window = open_window();
# one window opened on the current workspace
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 1, 'workspace contains one node');
# focus the workspace
cmd "focus parent";
cmd "focus parent";
# try to kill the workspace
cmd "kill";
sync_with_i3;
# the workspace should now be empty
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 0, 'workspace is empty');
################################################################################
# check if killing a workspace also closes floating windows.
################################################################################
$tmp = fresh_workspace;
$window = open_window;
my $floating_window = open_floating_window;
# one window opened on the current workspace
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$focus, 2, 'workspace contains two nodes');
# focus the workspace
cmd "focus parent";
cmd "focus parent";
# try to kill the workspace
cmd "kill";
sync_with_i3;
# the workspace should now be empty
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$focus, 0, 'workspace is empty');
##############################################################
# and now for something completely different:
# check if the pointer position is relevant when restoring focus

View File

@ -49,29 +49,9 @@ wait_for_unmap $window;
cmp_ok(@content, '==', 0, 'no more nodes');
diag('content = '. Dumper(\@content));
# TODO: move this to X11::XCB::Window
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"
);
}
$window = open_window(
name => 'Borderless window',
before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
wm_class => 'borderless',
);
@content = @{get_ws_content($tmp)};
@ -190,7 +170,7 @@ $tmp = fresh_workspace;
$window = open_window(
name => 'usethis',
before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
wm_class => 'borderless',
);
@content = @{get_ws_content($tmp)};
@ -208,8 +188,7 @@ sync_with_i3;
cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
$window->_create;
set_wm_class($window->id, 'borderless', 'borderless');
$window->wm_class('borderless');
$window->name('notthis');
$window->map;
wait_for_map $window;
@ -238,7 +217,8 @@ $tmp = fresh_workspace;
$window = open_window(
name => 'usethis',
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
wm_class => 'bar',
instance => 'foo',
);
@content = @{get_ws_content($tmp)};
@ -264,7 +244,8 @@ $tmp = fresh_workspace;
$window = open_window(
name => 'usethis',
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
wm_class => 'bar',
instance => 'foo',
);
@content = @{get_ws_content($tmp)};
@ -292,7 +273,8 @@ $tmp = fresh_workspace;
$window = open_window(
name => 'usethis',
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
wm_class => 'bar',
instance => 'foo',
);
@content = @{get_ws_content($tmp)};

View File

@ -17,37 +17,16 @@
# Tests if assignments work
#
use i3test i3_autostart => 0;
use X11::XCB qw(PROP_MODE_REPLACE);
# 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';
$args{name} //= 'special window';
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
%args,
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
wm_class => 'special',
dont_map => 1,
);
$window->map;

View File

@ -18,37 +18,16 @@
# assigned to an invisible workspace
#
use i3test i3_autostart => 0;
use X11::XCB qw(PROP_MODE_REPLACE);
# 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';
$args{name} //= 'special window';
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
%args,
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
wm_class => 'special',
dont_map => 1,
);
$window->map;

View File

@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv;
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
ok(!$bar_config->{verbose}, 'verbose off by default');
ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
is($bar_config->{mode}, 'dock', 'dock mode by default');
is($bar_config->{position}, 'bottom', 'position bottom by default');
@ -85,7 +86,8 @@ $config = <<EOT;
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bar {
# Start a default instance of i3bar which provides workspace buttons.
# Start a default instance of i3bar which does not provide
# workspace buttons.
# Additionally, i3status will provide a statusline.
status_command i3status --bar
@ -98,6 +100,7 @@ bar {
mode dock
font Terminus
workspace_buttons no
binding_mode_indicator no
verbose yes
socket_path /tmp/foobar
@ -125,6 +128,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
ok($bar_config->{verbose}, 'verbose on');
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled');
is($bar_config->{mode}, 'dock', 'dock mode');
is($bar_config->{position}, 'top', 'position top');
is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
@ -230,7 +234,8 @@ $config = <<EOT;
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bar {
# Start a default instance of i3bar which provides workspace buttons.
# Start a default instance of i3bar which does not provide
# workspace buttons.
# Additionally, i3status will provide a statusline.
status_command i3status --bar
@ -243,6 +248,7 @@ bar {
mode dock
font Terminus
workspace_buttons no
binding_mode_indicator yes
verbose yes
socket_path /tmp/foobar
@ -271,6 +277,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
ok($bar_config->{verbose}, 'verbose on');
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled');
is($bar_config->{mode}, 'dock', 'dock mode');
is($bar_config->{position}, 'top', 'position top');
is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');

View File

@ -446,4 +446,29 @@ is(get_focused($ws), $scratch, 'scratchpad is focused');
# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
################################################################################
# 14: Verify that 'move scratchpad' sends floating containers to scratchpad but
# does not resize/resposition the container on the next 'scratchpad show', i.e.,
# i3 sets the scratchpad flag to SCRATCHPAD_CHANGED
################################################################################
clear_scratchpad;
$tmp = fresh_workspace;
open_window;
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 1, 'precisely one window on current ws');
is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
cmd 'floating toggle';
cmd 'move scratchpad';
$__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 0, 'no window on current ws anymore');
is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
done_testing;

View File

@ -171,4 +171,19 @@ is(parser_calls('workspace "foo \"bar"'),
'cmd_workspace_name(foo "bar)',
'Command with escaped double quotes ok');
################################################################################
# 4: Verify that resize commands with a "px or ppt"-construction are parsed
# correctly
################################################################################
is(parser_calls("resize shrink width 10 px or"),
"ERROR: Expected one of these tokens: <word>\n" .
"ERROR: Your command: resize shrink width 10 px or\n" .
"ERROR: ",
"error for resize command with incomplete 'or'-construction ok");
is(parser_calls("resize grow left 10 px or 20 ppt"),
"cmd_resize(grow, left, 10, 20)",
"resize command with 'or'-construction ok");
done_testing;

View File

@ -18,44 +18,13 @@
# window is shown on another workspace.
#
use i3test;
use List::Util qw(first);
use X11::XCB qw(:all);
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# 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 $win = open_window;
my $scratch = open_special;
my $scratch = open_window(wm_class => 'special');
cmd '[class="special"] move scratchpad';
is_num_children($tmp, 1, 'one window on current ws');

View File

@ -85,6 +85,7 @@ my $w2 = open_window;
is($x->input_focus, $w2->id, 'window 2 focused');
cmd "workspace $tmp2";
$w->delete_hint('urgency');
$w->add_hint('urgency');
sync_with_i3;

View File

@ -627,7 +627,7 @@ EOT
$expected = <<'EOT';
cfg_bar_output(LVDS-1)
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'verbose', 'colors', '}'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1

View File

@ -19,7 +19,6 @@
# Ticket: #909
# Bug still in: 4.4-69-g6856b23
use i3test i3_autostart => 0;
use X11::XCB qw(:all);
my $config = <<EOT;
# i3 config file (v4)
@ -31,28 +30,10 @@ EOT
my $pid = launch_with_config($config);
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"
);
}
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
before_map => sub { set_wm_class($_->id, '__i3-test-window', '__i3-test-window') },
wm_class => '__i3-test-window',
dont_map => 1,
);
$window->map;

View File

@ -20,41 +20,11 @@
# 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 $scratch = open_window(wm_class => 'special');
my $tmp2 = fresh_workspace;

View File

@ -19,7 +19,6 @@
# Ticket: #1027
# Bug still in: 4.5.1-90-g6582da9
use i3test i3_autostart => 0;
use X11::XCB qw(PROP_MODE_REPLACE);
my $config = <<'EOT';
# i3 config file (v4)
@ -31,28 +30,9 @@ EOT
my $pid = launch_with_config($config);
# TODO: move this to X11::XCB::Window
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"
);
}
my $window = open_window(
name => 'Borderless window',
before_map => sub { set_wm_class($_->id, 'special', 'special') },
wm_class => 'special',
dont_map => 1,
);
$window->map;

View File

@ -21,37 +21,16 @@
# Ticket: #1086
# Bug still in: 4.6-62-g7098ef6
use i3test i3_autostart => 0;
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';
$args{name} //= 'special window';
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
%args,
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
wm_class => 'special',
dont_map => 1,
);
$window->add_hint('urgency');

View File

@ -0,0 +1,113 @@
#!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 if the urgency hint will be set appropriately when opening a window
# assigned to a workspace.
#
use i3test i3_autostart => 0;
# Based on the eponymous function in t/166-assign.t
sub open_special {
my %args = @_;
$args{name} //= 'special window';
# We use dont_map because i3 will not map the window on the current
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
my $window = open_window(
%args,
wm_class => 'special',
dont_map => 1,
);
$window->map;
return $window;
}
#####################################################################
# start a window assigned to a non-visible workspace and see that the urgency
# hint is set.
#####################################################################
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
assign [class="special"] targetws
EOT
my $pid = launch_with_config($config);
cmd 'workspace ordinaryws';
my $window = open_special;
sync_with_i3;
ok(get_ws('targetws')->{urgent}, 'target workspace is urgent');
$window->destroy;
exit_gracefully($pid);
#####################################################################
# start a window assigned to a visible workspace and see that the urgency hint
# is not set.
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
assign [class="special"] targetws
EOT
$pid = launch_with_config($config);
cmd 'workspace targetws';
$window = open_special;
sync_with_i3;
ok(!get_ws('targetws')->{urgent}, 'visible workspace is not urgent');
$window->destroy;
exit_gracefully($pid);
#####################################################################
# start a window assigned to a visible workspace on a different output and see
# that the urgency hint is not set.
#####################################################################
$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
workspace targetws output fake-0
workspace ordinaryws output fake-1
assign [class="special"] targetws
EOT
$pid = launch_with_config($config);
cmd 'workspace ordinaryws';
$window = open_special;
sync_with_i3;
ok(!get_ws('targetws')->{urgent}, 'target workspace is not urgent');
$window->destroy;
exit_gracefully($pid);
done_testing;

View File

@ -71,6 +71,23 @@ is(focused_output, 'fake-1', 'focus on second output');
cmd 'focus output fake-0';
is(focused_output, 'fake-0', 'focus on first output');
################################################################################
# use 'focus output' and verify that i3 does not crash when the currently
# focused window is floating and is only partially mapped on an output screen
################################################################################
is(focused_output, 'fake-0', 'focus on first output');
my $floating_win = open_window;
cmd 'floating toggle';
cmd 'move to absolute position -10 -10';
cmd 'focus output right';
is(focused_output, 'fake-1', 'focus on second output');
cmd 'focus output fake-0';
is(focused_output, 'fake-0', 'focus on first output');
exit_gracefully($pid);
done_testing;

View File

@ -19,6 +19,10 @@
use i3test i3_autostart => 0;
# 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
@ -35,7 +39,7 @@ $i3->connect()->recv;
# Workspaces requests and events
################################
my $focused = get_ws(focused_ws());
my $old_ws = get_ws(focused_ws);
# Events
@ -46,17 +50,11 @@ $i3->subscribe({
workspace => sub {
my ($event) = @_;
if ($event->{change} eq 'focus') {
# Check that we have the old and new workspace
$focus->send(
$event->{current}->{name} == '2' &&
$event->{old}->{name} == $focused->{name}
);
$focus->send($event);
}
}
})->recv;
cmd 'focus output right';
my $t;
$t = AnyEvent->timer(
after => 0.5,
@ -65,7 +63,15 @@ $t = AnyEvent->timer(
}
);
ok($focus->recv, 'Workspace "focus" event received');
cmd 'focus output right';
my $event = $focus->recv;
my $current_ws = get_ws(focused_ws);
ok($event, 'Workspace "focus" event received');
is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
exit_gracefully($pid);

View File

@ -0,0 +1,40 @@
#!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 that new workspace names are taken from the config,
# then from the first free number starting with 1.
#
use i3test i3_autostart => 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
bindsym 1 workspace 1: eggs
EOT
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path());
my $ws = $i3->get_workspaces->recv;
is($ws->[0]->{name}, '1: eggs', 'new workspace uses config name');
is($ws->[1]->{name}, '2', 'naming continues with next free number');
exit_gracefully($pid);
done_testing;

106
testcases/t/516-move.t Normal file
View File

@ -0,0 +1,106 @@
#!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 if a simple 'move <direction>' command will move containers across outputs.
#
use i3test i3_autostart => 0;
# 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,1024x768+1024+768,1024x768+0+768
workspace left-top output fake-0
workspace right-top output fake-1
workspace right-bottom output fake-2
workspace left-bottom output fake-3
EOT
my $pid = launch_with_config($config);
#####################################################################
# Try to move a single window across outputs in each direction
#####################################################################
cmd('workspace left-top');
my $alone_window = open_window;
cmd('move right');
is(scalar @{get_ws_content('right-top')}, 1, 'moved individual window to right-top workspace');
cmd('move down');
is(scalar @{get_ws_content('right-bottom')}, 1, 'moved individual window to right-bottom workspace');
cmd('move left');
is(scalar @{get_ws_content('left-bottom')}, 1, 'moved individual window to left-bottom workspace');
cmd('move up');
is(scalar @{get_ws_content('left-top')}, 1, 'moved individual window to left-top workspace');
$alone_window->unmap;
wait_for_unmap;
#####################################################################
# Try to move a window on a workspace with two windows across outputs in each
# direction
#####################################################################
# from left-top to right-top
cmd('workspace left-top');
cmd('split h');
my $first_window = open_window;
my $social_window = open_window( name => 'CORRECT_WINDOW' );
cmd('move right');
is(scalar @{get_ws_content('right-top')}, 1, 'moved some window to right-top workspace');
my $compare_window = shift @{get_ws_content('right-top')};
is($compare_window->{window}, $social_window->id, 'moved correct window to right-top workspace');
# unmap the first window so we don't confuse it when we move back here
$first_window->unmap;
wait_for_unmap;
# from right-top to right-bottom
cmd('split v');
open_window;
# this window opened above - we need to move down twice
cmd('focus up; move down; move down');
is(scalar @{get_ws_content('right-bottom')}, 1, 'moved some window to right-bottom workspace');
$compare_window = shift @{get_ws_content('right-bottom')};
is($compare_window->{name}, $social_window->name, 'moved correct window to right-bottom workspace');
# from right-bottom to left-bottom
cmd('split h');
open_window;
cmd('focus left; move left');
is(scalar @{get_ws_content('left-bottom')}, 1, 'moved some window to left-bottom workspace');
$compare_window = shift @{get_ws_content('left-bottom')};
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
# from left-bottom to left-top
cmd('split v');
open_window;
cmd('focus up; move up');
is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom workspace');
$compare_window = shift @{get_ws_content('left-top')};
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
exit_gracefully($pid);
done_testing;