Merge branch 'next' into master

This commit is contained in:
Michael Stapelberg 2015-03-29 19:07:24 +02:00
commit 8629c2e208
92 changed files with 1693 additions and 891 deletions

47
.travis.yml Normal file
View File

@ -0,0 +1,47 @@
language: c
compiler:
- gcc
- clang
before_install:
# The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get
# into a state where we can build a recent version of i3 :(.
- "echo 'deb http://archive.ubuntu.com/ubuntu/ trusty main universe' | sudo tee /etc/apt/sources.list.d/trusty.list"
- "echo 'APT::Default-Release \"precise\";' | sudo tee /etc/apt/apt.conf.d/default-release"
- "echo 'deb http://archive.ubuntu.com/ubuntu/ utopic main universe' | sudo tee /etc/apt/sources.list.d/utopic.list"
- "echo 'Package: libc6' > /tmp/pin"
- "echo 'Pin: release n=trusty' >> /tmp/pin"
- "echo 'Pin-Priority: 999' >> /tmp/pin"
- "echo '' >> /tmp/pin"
- "echo 'Package: libxkbcommon*' >> /tmp/pin"
- "echo 'Pin: release n=trusty' >> /tmp/pin"
- "echo 'Pin-Priority: 999' >> /tmp/pin"
- "echo '' >> /tmp/pin"
- "echo 'Package: libyajl*' >> /tmp/pin"
- "echo 'Pin: release n=trusty' >> /tmp/pin"
- "echo 'Pin-Priority: 999' >> /tmp/pin"
- "echo '' >> /tmp/pin"
- "echo 'Package: libxcb-image*' >> /tmp/pin"
- "echo 'Pin: release n=trusty' >> /tmp/pin"
- "echo 'Pin-Priority: 999' >> /tmp/pin"
- "echo '' >> /tmp/pin"
- sudo cp /tmp/pin /etc/apt/preferences.d/trustypin
- sudo apt-get update
- sudo apt-get install -t trusty libc6 libc6-dev
- sudo apt-get install --no-install-recommends devscripts equivs
- sudo apt-get install -t utopic clang-format-3.5
- clang-format-3.5 --version
install:
- sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control
# Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug.
- sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl
- sudo /bin/sh -c 'cpanm -n -v X11::XCB || true'
- sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true'
script:
- CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Werror" make -j
- (cd testcases && xvfb-run ./complete-run.pl --parallel=1 || (cat latest/complete-run.log; false))
- clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)

68
RELEASE-NOTES-4.10.1 Normal file
View File

@ -0,0 +1,68 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.10.1 │
└──────────────────────────────┘
This is i3 v4.10.1. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
This release contains mostly bugfixes, but we felt it was necessary since there
are two important changes in behavior: we have reverted the pango markup
parsing by default (introduced with i3 v4.9) and the change in how the
“workspace” command behaves (introduced with i3 v4.9). Both of them broke some
users setups, which is not acceptable. In order to help us avoid such mistakes
in the future, please consider using the i3 git version — it is typically
stable.
PS: The v4.10 release did not contain any of the commits we meant to release
due to a human error in our release automation. Hence the v4.10.1 release.
┌────────────────────────────┐
│ Changes in i3 v4.10.1 │
└────────────────────────────┘
• i3bar: cut long statuslines from the left
• i3bar: add support for the short_text property
• i3-sensible-terminal: launch i3-nagbar when no terminal is found
• i3-config-wizard: switch modifier on key up/down
• docs/layout-saving: added a troubleshooting section
• docs: degender all the terms
• Revert "Workspace command number selection"
• dont parse blocks as markup by default
• Allow escaping backslashes in commands.
• switch default font from “DejaVu Sans Mono 8” to “monospace 8”, which is
typically a synonym, except for users who prefer a different font.
• When renaming a workspace, look for assignments and move the renamed
workspace to the appropriate output.
• i3-save-tree: make --workspace optional by defaulting to the focused
workspace
• Allow nop command without argument
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3bar: buffer the statusline to avoid flickering
• i3bar: fix click events for workspace buttons with long statusline
• i3bar: set correct initial position when reconfiguring
• i3bar: reconfigure strut partial on reload
• i3-nagbar: fix sizes/positioning on hi-dpi displays
• i3-config-wizard: fix sizes/positioning on hi-dpi displays
• i3-input: fix sizes/positioning on hi-dpi displays
• Fix scrolling in window decoration with hidden cursor.
• workspace rename focus mismatch
• Dont overwrite border width when already set (placeholders).
• fix a segfault during config file validation
• Restore placeholder windows after restarting.
• Dont focus placeholder windows.
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Chih-Chyuan Hwang, Deiz, Diana Dinosaur, Ingo Bürk, Michael Hofmann,
Michael Tipton, Micha Rosenbaum, shdown, Tony Crisci
-- Michael Stapelberg, 2015-03-29

View File

@ -1,36 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.9.1 │
└──────────────────────────────┘
This is i3 v4.9.1. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
This is a bugfix release for i3 v4.9.
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3bar: fix incorrect y-offset for text
• fix key bindings on big-endian platforms
• fix key bindings using Mode_switch
• fix keyboard layout change detection
• revert "Handle WM_CHANGE_STATE requests for iconic state" (fixes problems
with application windows disappearing, like SDL-based games when switching
workspaces)
• insert id-based match at HEAD, not TAIL (fixes window swallowing not
working when the criteria match the placeholder window)
• improve error messages on failing commands
• replace ~ in filepath when calling append_layout
• properly error out when the layout file cannot be read
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Steven McDonald, Ton van den Heuvel, Ingo Bürk
-- Michael Stapelberg, 2015-03-07

16
debian/changelog vendored
View File

@ -1,8 +1,20 @@
i3-wm (4.9.1-1) experimental; urgency=medium i3-wm (4.10.1-1) experimental; urgency=medium
* NOT YET RELEASED. * NOT YET RELEASED.
-- Michael Stapelberg <stapelberg@debian.org> Sat, 28 Feb 2015 15:04:25 +0100 -- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 18:08:13 +0200
i3-wm (4.10-1) experimental; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Mar 2015 17:46:09 +0200
i3-wm (4.9.1-1) experimental; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Sat, 07 Mar 2015 20:01:46 +0100
i3-wm (4.9-1) experimental; urgency=medium i3-wm (4.9-1) experimental; urgency=medium

3
debian/control vendored
View File

@ -40,8 +40,7 @@ Package: i3-wm
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, x11-utils
Provides: x-window-manager Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl
Description: improved dynamic tiling window manager Description: improved dynamic tiling window manager
Key features of i3 are good documentation, reasonable defaults (changeable in Key features of i3 are good documentation, reasonable defaults (changeable in
a simple configuration file) and good multi-monitor support. The user a simple configuration file) and good multi-monitor support. The user

View File

@ -1012,7 +1012,7 @@ gets started in any way) and the window(s) which appear.
Imagine for example using dmenu: The user starts dmenu by pressing Mod+d, dmenu Imagine for example using dmenu: The user starts dmenu by pressing Mod+d, dmenu
gets started with PID 3390. The user then decides to launch Firefox, which gets started with PID 3390. The user then decides to launch Firefox, which
takes a long time. So he enters firefox into dmenu and presses enter. Firefox takes a long time. So they enter firefox into dmenu and press enter. Firefox
gets started with PID 4001. When it finally finishes loading, it creates an X11 gets started with PID 4001. When it finally finishes loading, it creates an X11
window and uses MapWindow to make it visible. This is the first time i3 window and uses MapWindow to make it visible. This is the first time i3
actually gets in touch with Firefox. It decides to map the window, but it has actually gets in touch with Firefox. It decides to map the window, but it has

View File

@ -117,10 +117,8 @@ click_events::
=== Blocks in detail === Blocks in detail
full_text:: full_text::
The most simple block you can think of is one which just includes the The +full_text+ will be displayed by i3bar on the status line. This is the
only required key, the +full_text+ key. i3bar will display the string only required key.
value parsed as
https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
short_text:: short_text::
Where appropriate, the +short_text+ (string) entry should also be Where appropriate, the +short_text+ (string) entry should also be
provided. It will be used in case the status line needs to be shortened provided. It will be used in case the status line needs to be shortened
@ -172,6 +170,10 @@ separator_block_width::
this gap, a separator line will be drawn unless +separator+ is this gap, a separator line will be drawn unless +separator+ is
disabled. Normally, you want to set this to an odd value (the default disabled. Normally, you want to set this to an odd value (the default
is 9 pixels), since the separator line is drawn in the middle. is 9 pixels), since the separator line is drawn in the middle.
markup::
A string that indicates how the text of the block should be parsed. Set to
+"pango"+ to use https://developer.gnome.org/pango/stable/PangoMarkupFormat.html[Pango markup].
Set to +"none"+ to not use any markup (default).
If you want to put in your own entries into a block, prefix the key with an If you want to put in your own entries into a block, prefix the key with an
underscore (_). i3bar will ignore all keys it doesnt understand, and prefixing underscore (_). i3bar will ignore all keys it doesnt understand, and prefixing

View File

@ -231,3 +231,31 @@ layouts, you can either use a JSON parser that supports these deviations (for
example libyajl), transform the layout file to a JSON-conforming file, or example libyajl), transform the layout file to a JSON-conforming file, or
link:http://cr.i3wm.org/[submit a patch] to make +i3-save-tree(1)+ optionally link:http://cr.i3wm.org/[submit a patch] to make +i3-save-tree(1)+ optionally
output standard-conforming JSON. output standard-conforming JSON.
== Troubleshooting
=== Restoring a vertically split workspace
When using +i3-save-tree+ with the +--workspace+ switch, only the *contents* of
the workspace will be dumped. This means that properties of the workspace
itself will be lost.
This is relevant for, e.g., a vertically split container as the base container of
a workspace. Since the split mode is a property of the workspace, it will not be
stored. In this case, you will have to manually wrap your layout in such a
container:
--------------------------------------------------------------------------------
// vim:ts=4:sw=4:et
{
// this is a manually added container to restore the vertical split
"layout": "splitv",
"percent": 0.5,
"type": "con",
"nodes": [
// the dumped workspace layout goes here
]
}
--------------------------------------------------------------------------------

View File

@ -1249,6 +1249,24 @@ bar {
} }
-------------------------------------------------------------- --------------------------------------------------------------
=== Custom separator symbol
Specifies a custom symbol to be used for the separator as opposed to the vertical,
one pixel thick separator. Note that you may have to adjust the +sep_block_width+
property.
*Syntax*:
-------------------------
separator_symbol <symbol>
-------------------------
*Example*:
------------------------
bar {
separator_symbol ":|:"
}
------------------------
=== Workspace buttons === Workspace buttons
Specifies whether workspace buttons should be shown or not. This is useful if Specifies whether workspace buttons should be shown or not. This is useful if
@ -2011,6 +2029,27 @@ bindsym $mod+minus scratchpad show
bindsym mod4+s [title="^Sup ::"] scratchpad show bindsym mod4+s [title="^Sup ::"] scratchpad show
------------------------------------------------ ------------------------------------------------
=== Nop
There is a no operation command +nop+ which allows you to override default
behavior. This can be useful for, e.g., disabling a focus change on clicks with
the middle mouse button.
The optional +comment+ argument is ignored, but will be printed to the log file
for debugging purposes.
*Syntax*:
---------------
nop [<comment>]
---------------
*Example*:
----------------------------------------------
# Disable focus change for clicks on titlebars
# with the middle mouse button
bindsym button2 nop
----------------------------------------------
=== i3bar control === i3bar control
There are two options in the configuration of each i3bar instance that can be There are two options in the configuration of each i3bar instance that can be

View File

@ -67,6 +67,11 @@
#include "xcb.h" #include "xcb.h"
#include "libi3.h" #include "libi3.h"
#define row_y(row) \
(((row)-1) * font.height + logical_px(4))
#define window_height() \
(row_y(15) + font.height)
enum { STEP_WELCOME, enum { STEP_WELCOME,
STEP_GENERATE } current_step = STEP_WELCOME; STEP_GENERATE } current_step = STEP_WELCOME;
enum { MOD_Mod1, enum { MOD_Mod1,
@ -80,6 +85,7 @@ xcb_screen_t *root_screen;
static xcb_get_modifier_mapping_reply_t *modmap_reply; static xcb_get_modifier_mapping_reply_t *modmap_reply;
static i3Font font; static i3Font font;
static i3Font bold_font; static i3Font bold_font;
static int char_width;
static char *socket_path; static char *socket_path;
static xcb_window_t win; static xcb_window_t win;
static xcb_pixmap_t pixmap; static xcb_pixmap_t pixmap;
@ -493,70 +499,70 @@ static char *resolve_tilde(const char *path) {
*/ */
static int handle_expose() { static int handle_expose() {
/* re-draw the background */ /* re-draw the background */
xcb_rectangle_t border = {0, 0, 300, (15 * font.height) + 8}; xcb_rectangle_t border = {0, 0, logical_px(300), window_height()};
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {get_colorpixel("#000000")}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
set_font(&font); set_font(&font);
#define txt(x, row, text) \ #define txt(x, row, text) \
draw_text_ascii(text, pixmap, pixmap_gc, \ draw_text_ascii(text, pixmap, pixmap_gc, \
x, (row - 1) * font.height + 4, 300 - x * 2) x, row_y(row), logical_px(500) - x * 2)
if (current_step == STEP_WELCOME) { if (current_step == STEP_WELCOME) {
/* restore font color */ /* restore font color */
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
txt(10, 2, "You have not configured i3 yet."); txt(logical_px(10), 2, "You have not configured i3 yet.");
txt(10, 3, "Do you want me to generate ~/.i3/config?"); txt(logical_px(10), 3, "Do you want me to generate ~/.i3/config?");
txt(85, 5, "Yes, generate ~/.i3/config"); txt(logical_px(85), 5, "Yes, generate ~/.i3/config");
txt(85, 7, "No, I will use the defaults"); txt(logical_px(85), 7, "No, I will use the defaults");
/* green */ /* green */
set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
txt(25, 5, "<Enter>"); txt(logical_px(25), 5, "<Enter>");
/* red */ /* red */
set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
txt(31, 7, "<ESC>"); txt(logical_px(31), 7, "<ESC>");
} }
if (current_step == STEP_GENERATE) { if (current_step == STEP_GENERATE) {
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
txt(10, 2, "Please choose either:"); txt(logical_px(10), 2, "Please choose either:");
txt(85, 4, "Win as default modifier"); txt(logical_px(85), 4, "Win as default modifier");
txt(85, 5, "Alt as default modifier"); txt(logical_px(85), 5, "Alt as default modifier");
txt(10, 7, "Afterwards, press"); txt(logical_px(10), 7, "Afterwards, press");
txt(85, 9, "to write ~/.i3/config"); txt(logical_px(85), 9, "to write ~/.i3/config");
txt(85, 10, "to abort"); txt(logical_px(85), 10, "to abort");
/* the not-selected modifier */ /* the not-selected modifier */
if (modifier == MOD_Mod4) if (modifier == MOD_Mod4)
txt(31, 5, "<Alt>"); txt(logical_px(31), 5, "<Alt>");
else else
txt(31, 4, "<Win>"); txt(logical_px(31), 4, "<Win>");
/* the selected modifier */ /* the selected modifier */
set_font(&bold_font); set_font(&bold_font);
set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
if (modifier == MOD_Mod4) if (modifier == MOD_Mod4)
txt(10, 4, "-> <Win>"); txt(logical_px(10), 4, "-> <Win>");
else else
txt(10, 5, "-> <Alt>"); txt(logical_px(10), 5, "-> <Alt>");
/* green */ /* green */
set_font(&font); set_font(&font);
set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
txt(25, 9, "<Enter>"); txt(logical_px(25), 9, "<Enter>");
/* red */ /* red */
set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000")); set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
txt(31, 10, "<ESC>"); txt(logical_px(31), 10, "<ESC>");
} }
/* Copy the contents of the pixmap to the real window */ /* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500); xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500));
xcb_flush(conn); xcb_flush(conn);
return 1; return 1;
@ -592,6 +598,12 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
finish(); finish();
} }
/* Swap between modifiers when up or down is pressed. */
if (sym == XK_Up || sym == XK_Down) {
modifier = (modifier == MOD_Mod1) ? MOD_Mod4 : MOD_Mod1;
handle_expose();
}
/* cancel any time */ /* cancel any time */
if (sym == XK_Escape) if (sym == XK_Escape)
exit(0); exit(0);
@ -637,14 +649,16 @@ static void handle_button_press(xcb_button_press_event_t *event) {
if (current_step != STEP_GENERATE) if (current_step != STEP_GENERATE)
return; return;
if (event->event_x >= 32 && event->event_x <= 68 && if (event->event_x < logical_px(32) ||
event->event_y >= 45 && event->event_y <= 54) { event->event_x > (logical_px(32) + char_width * 5))
return;
if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
modifier = MOD_Mod4; modifier = MOD_Mod4;
handle_expose(); handle_expose();
} }
if (event->event_x >= 32 && event->event_x <= 68 && if (event->event_y >= row_y(5) && event->event_y <= (row_y(5) + font.height)) {
event->event_y >= 56 && event->event_y <= 70) {
modifier = MOD_Mod1; modifier = MOD_Mod1;
handle_expose(); handle_expose();
} }
@ -760,8 +774,8 @@ static void finish() {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
config_path = resolve_tilde("~/.i3/config"); config_path = resolve_tilde("~/.i3/config");
socket_path = getenv("I3SOCK"); socket_path = getenv("I3SOCK");
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; char *pattern = "pango:monospace 8";
char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1"; char *patternbold = "pango:monospace bold 8";
int o, option_index = 0; int o, option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
@ -858,6 +872,10 @@ int main(int argc, char *argv[]) {
font = load_font(pattern, true); font = load_font(pattern, true);
bold_font = load_font(patternbold, true); bold_font = load_font(patternbold, true);
/* Determine character width in the default font. */
set_font(&font);
char_width = predict_text_width(i3string_from_utf8("a"));
/* Open an input window */ /* Open an input window */
win = xcb_generate_id(conn); win = xcb_generate_id(conn);
xcb_create_window( xcb_create_window(
@ -865,12 +883,12 @@ int main(int argc, char *argv[]) {
XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
win, /* the window id */ win, /* the window id */
root, /* parent == root */ root, /* parent == root */
490, 297, 300, 205, /* dimensions */ logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */
0, /* X11 border = 0, we draw our own */ 0, /* X11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
(uint32_t[]) { (uint32_t[]){
0, /* back pixel: black */ 0, /* back pixel: black */
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_BUTTON_PRESS}); XCB_EVENT_MASK_BUTTON_PRESS});
@ -914,7 +932,7 @@ int main(int argc, char *argv[]) {
/* Create pixmap */ /* Create pixmap */
pixmap = xcb_generate_id(conn); pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn); pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500); xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Grab the keyboard to get all input */ /* Grab the keyboard to get all input */

View File

@ -306,7 +306,7 @@ for my $app (keys %apps) {
# }; # };
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Run dmenu to ask the user for her choice # ┃ Run dmenu to ask the user for their choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# open2 will just make dmenus STDERR go to our own STDERR. # open2 will just make dmenus STDERR go to our own STDERR.

View File

@ -43,8 +43,7 @@ static int check_for_wrap(void) {
* of the log. */ * of the log. */
wrap_count = header->wrap_count; wrap_count = header->wrap_count;
const int len = (logbuffer + header->offset_last_wrap) - walk; const int len = (logbuffer + header->offset_last_wrap) - walk;
if (write(STDOUT_FILENO, walk, len) != len) swrite(STDOUT_FILENO, walk, len);
err(EXIT_FAILURE, "write()");
walk = logbuffer + sizeof(i3_shmlog_header); walk = logbuffer + sizeof(i3_shmlog_header);
return 1; return 1;
} }
@ -52,12 +51,8 @@ static int check_for_wrap(void) {
static void print_till_end(void) { static void print_till_end(void) {
check_for_wrap(); check_for_wrap();
const int len = (logbuffer + header->offset_next_write) - walk; const int len = (logbuffer + header->offset_next_write) - walk;
const int n = write(STDOUT_FILENO, walk, len); swrite(STDOUT_FILENO, walk, len);
if (len != n) walk += len;
err(EXIT_FAILURE, "write()");
if (n > 0) {
walk += n;
}
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {

View File

@ -129,10 +129,11 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
printf("expose!\n"); printf("expose!\n");
/* re-draw the background */ /* re-draw the background */
xcb_rectangle_t border = {0, 0, 500, font.height + 8}, inner = {2, 2, 496, font.height + 8 - 4}; xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)},
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {get_colorpixel("#FF0000")}); inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)};
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {get_colorpixel("#000000")}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */ /* restore font color */
@ -140,17 +141,17 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
/* draw the prompt … */ /* draw the prompt … */
if (prompt != NULL) { if (prompt != NULL) {
draw_text(prompt, pixmap, pixmap_gc, 4, 4, 492); draw_text(prompt, pixmap, pixmap_gc, logical_px(4), logical_px(4), logical_px(492));
} }
/* … and the text */ /* … and the text */
if (input_position > 0) { if (input_position > 0) {
i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
draw_text(input, pixmap, pixmap_gc, prompt_offset + 4, 4, 492); draw_text(input, pixmap, pixmap_gc, prompt_offset + logical_px(4), logical_px(4), logical_px(492));
i3string_free(input); i3string_free(input);
} }
/* Copy the contents of the pixmap to the real window */ /* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8); xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8));
xcb_flush(conn); xcb_flush(conn);
return 1; return 1;
@ -234,6 +235,9 @@ static void finish_input() {
static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
printf("Keypress %d, state raw = %d\n", event->detail, event->state); printf("Keypress %d, state raw = %d\n", event->detail, event->state);
// TODO: port the input handling code from i3lock once libxkbcommon ≥ 0.5.0
// is available in distros.
/* See the documentation of xcb_key_symbols_get_keysym for this one. /* See the documentation of xcb_key_symbols_get_keysym for this one.
* Basically: We get either col 0 or col 1, depending on whether shift is * Basically: We get either col 0 or col 1, depending on whether shift is
* pressed. */ * pressed. */
@ -313,7 +317,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
format = strdup("%s"); format = strdup("%s");
socket_path = getenv("I3SOCK"); socket_path = getenv("I3SOCK");
char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"); char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0; int o, option_index = 0;
static struct option long_options[] = { static struct option long_options[] = {
@ -405,12 +409,12 @@ int main(int argc, char *argv[]) {
XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
win, /* the window id */ win, /* the window id */
root, /* parent == root */ root, /* parent == root */
50, 50, 500, font.height + 8, /* dimensions */ logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8), /* dimensions */
0, /* X11 border = 0, we draw our own */ 0, /* X11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK,
(uint32_t[]) { (uint32_t[]){
0, /* back pixel: black */ 0, /* back pixel: black */
1, /* override redirect: dont manage this window */ 1, /* override redirect: dont manage this window */
XCB_EVENT_MASK_EXPOSURE}); XCB_EVENT_MASK_EXPOSURE});
@ -421,7 +425,7 @@ int main(int argc, char *argv[]) {
/* Create pixmap */ /* Create pixmap */
pixmap = xcb_generate_id(conn); pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn); pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8); xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Set input focus (we have override_redirect=1, so the wm will not do /* Set input focus (we have override_redirect=1, so the wm will not do

View File

@ -341,7 +341,7 @@ sub convert_command {
# NOTE: This is not 100% accurate, as it only works for one level # NOTE: This is not 100% accurate, as it only works for one level
# of nested containers. As this is a common use case, we use 'focus # of nested containers. As this is a common use case, we use 'focus
# parent; $command' nevertheless. For advanced use cases, the user # parent; $command' nevertheless. For advanced use cases, the user
# has to modify his config. # has to modify their config.
print "$statement $key focus parent; $command\n"; print "$statement $key focus parent; $command\n";
} }
return; return;

View File

@ -5,7 +5,7 @@
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE) * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
* *
* i3-nagbar is a utility which displays a nag message, for example in the case * i3-nagbar is a utility which displays a nag message, for example in the case
* when the user has an error in his configuration file. * when the user has an error in their configuration file.
* *
*/ */
#include <stdio.h> #include <stdio.h>
@ -131,7 +131,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
printf("button released on x = %d, y = %d\n", printf("button released on x = %d, y = %d\n",
event->event_x, event->event_y); event->event_x, event->event_y);
/* If the user hits the close button, we exit(0) */ /* If the user hits the close button, we exit(0) */
if (event->event_x >= (rect.width - 32)) if (event->event_x >= (rect.width - logical_px(32)))
exit(0); exit(0);
button_t *button = get_button_at(event->event_x, event->event_y); button_t *button = get_button_at(event->event_x, event->event_y);
if (!button) if (!button)
@ -164,7 +164,9 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
char *link_path; char *link_path;
char *exe_path = get_exe_path(argv0); char *exe_path = get_exe_path(argv0);
sasprintf(&link_path, "%s.nagbar_cmd", script_path); sasprintf(&link_path, "%s.nagbar_cmd", script_path);
symlink(exe_path, link_path); if (symlink(exe_path, link_path) == -1) {
err(EXIT_FAILURE, "Failed to symlink %s to %s", link_path, exe_path);
}
char *terminal_cmd; char *terminal_cmd;
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
@ -188,21 +190,23 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
*/ */
static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
/* re-draw the background */ /* re-draw the background */
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {color_background}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
/* restore font color */ /* restore font color */
set_font_colors(pixmap_gc, color_text, color_background); set_font_colors(pixmap_gc, color_text, color_background);
draw_text(prompt, pixmap, pixmap_gc, draw_text(prompt, pixmap, pixmap_gc,
4 + 4, 4 + 4, rect.width - 4 - 4); logical_px(4) + logical_px(4),
logical_px(4) + logical_px(4),
rect.width - logical_px(4) - logical_px(4));
/* render close button */ /* render close button */
const char *close_button_label = "X"; const char *close_button_label = "X";
int line_width = 4; int line_width = logical_px(4);
/* set width to the width of the label */ /* set width to the width of the label */
int w = predict_text_width(i3string_from_utf8(close_button_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 */ /* account for left/right padding, which seems to be set to 8px (total) below */
w += 8; w += logical_px(8);
int y = rect.width; int y = rect.width;
uint32_t values[3]; uint32_t values[3];
values[0] = color_button_background; values[0] = color_button_background;
@ -212,23 +216,25 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height};
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {color_border}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border});
xcb_point_t points[] = { xcb_point_t points[] = {
{y - w - (2 * line_width), line_width / 2}, {y - w - (2 * line_width), line_width / 2},
{y - (line_width / 2), line_width / 2}, {y - (line_width / 2), line_width / 2},
{y - (line_width / 2), (rect.height - (line_width / 2)) - 2}, {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)},
{y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2}, {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)},
{y - w - (2 * line_width), line_width / 2}}; {y - w - (2 * line_width), line_width / 2}};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
values[0] = 1; values[0] = 1;
set_font_colors(pixmap_gc, color_text, color_button_background); set_font_colors(pixmap_gc, color_text, color_button_background);
/* the x term here seems to set left/right padding */ /* 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, draw_text_ascii(close_button_label, pixmap, pixmap_gc,
4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4); y - w - line_width + w / 2 - logical_px(4),
logical_px(4) + logical_px(3),
rect.width - y + w + line_width - w / 2 + logical_px(4));
y -= w; y -= w;
y -= 20; y -= logical_px(20);
/* render custom buttons */ /* render custom buttons */
line_width = 1; line_width = 1;
@ -236,21 +242,21 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
/* set w to the width of the label */ /* set w to the width of the label */
w = predict_text_width(buttons[c].label); w = predict_text_width(buttons[c].label);
/* account for left/right padding, which seems to be set to 12px (total) below */ /* account for left/right padding, which seems to be set to 12px (total) below */
w += 12; w += logical_px(12);
y -= 30; y -= logical_px(30);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {color_button_background}); 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}; close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)};
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {color_border}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border});
buttons[c].x = y - w - (2 * line_width); buttons[c].x = y - w - (2 * line_width);
buttons[c].width = w; buttons[c].width = w;
xcb_point_t points2[] = { xcb_point_t points2[] = {
{y - w - (2 * line_width), (line_width / 2) + 2}, {y - w - (2 * line_width), (line_width / 2) + logical_px(2)},
{y - (line_width / 2), (line_width / 2) + 2}, {y - (line_width / 2), (line_width / 2) + logical_px(2)},
{y - (line_width / 2), (rect.height - 4 - (line_width / 2))}, {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))},
{y - w - (2 * line_width), (rect.height - 4 - (line_width / 2))}, {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))},
{y - w - (2 * line_width), (line_width / 2) + 2}}; {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
values[0] = color_text; values[0] = color_text;
@ -258,13 +264,15 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
set_font_colors(pixmap_gc, color_text, color_button_background); set_font_colors(pixmap_gc, color_text, color_button_background);
/* the x term seems to set left/right padding */ /* the x term seems to set left/right padding */
draw_text(buttons[c].label, pixmap, pixmap_gc, draw_text(buttons[c].label, pixmap, pixmap_gc,
y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6); y - w - line_width + logical_px(6),
logical_px(4) + logical_px(3),
rect.width - y + w + line_width - logical_px(6));
y -= w; y -= w;
} }
/* border line at the bottom */ /* border line at the bottom */
line_width = 2; line_width = logical_px(2);
values[0] = color_border_bottom; values[0] = color_border_bottom;
values[1] = line_width; values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
@ -316,7 +324,7 @@ int main(int argc, char *argv[]) {
argv0 = argv[0]; argv0 = argv[0];
char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"); char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0; int o, option_index = 0;
enum { TYPE_ERROR = 0, enum { TYPE_ERROR = 0,
TYPE_WARNING = 1 } bar_type = TYPE_ERROR; TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
@ -410,12 +418,12 @@ int main(int argc, char *argv[]) {
XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
win, /* the window id */ win, /* the window id */
root, /* parent == root */ root, /* parent == root */
50, 50, 500, font.height + 8 + 8 /* 8 px padding */, /* dimensions */ 50, 50, 500, font.height + logical_px(8) + logical_px(8) /* 8 px padding */, /* dimensions */
0, /* x11 border = 0, we draw our own */ 0, /* x11 border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
(uint32_t[]) { (uint32_t[]){
0, /* back pixel: black */ 0, /* back pixel: black */
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
@ -465,7 +473,7 @@ int main(int argc, char *argv[]) {
} __attribute__((__packed__)) strut_partial; } __attribute__((__packed__)) strut_partial;
memset(&strut_partial, 0, sizeof(strut_partial)); memset(&strut_partial, 0, sizeof(strut_partial));
strut_partial.top = font.height + 6; strut_partial.top = font.height + logical_px(6);
strut_partial.top_start_x = 0; strut_partial.top_start_x = 0;
strut_partial.top_end_x = 800; strut_partial.top_end_x = 800;
@ -481,7 +489,7 @@ int main(int argc, char *argv[]) {
/* Create pixmap */ /* Create pixmap */
pixmap = xcb_generate_id(conn); pixmap = xcb_generate_id(conn);
pixmap_gc = xcb_generate_id(conn); pixmap_gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8); xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8));
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
/* Grab the keyboard to get all input */ /* Grab the keyboard to get all input */
@ -512,7 +520,7 @@ int main(int argc, char *argv[]) {
case XCB_CONFIGURE_NOTIFY: { case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
rect = (xcb_rectangle_t) { rect = (xcb_rectangle_t){
configure_notify->x, configure_notify->x,
configure_notify->y, configure_notify->y,
configure_notify->width, configure_notify->width,

View File

@ -13,6 +13,7 @@ use POSIX qw(locale_h);
use File::Find; use File::Find;
use File::Basename qw(basename); use File::Basename qw(basename);
use File::Temp qw(tempfile); use File::Temp qw(tempfile);
use List::Util qw(first);
use Getopt::Long; use Getopt::Long;
use Pod::Usage; use Pod::Usage;
use AnyEvent::I3; use AnyEvent::I3;
@ -41,11 +42,7 @@ my $result = GetOptions(
die "Could not parse command line options" unless $result; die "Could not parse command line options" unless $result;
if (!defined($workspace) && !defined($output)) { if (defined($workspace) && defined($output)) {
die "One of --workspace or --output need to be specified";
}
unless (defined($workspace) ^ defined($output)) {
die "Only one of --workspace or --output can be specified"; die "Only one of --workspace or --output can be specified";
} }
@ -57,6 +54,15 @@ if (!$i3->connect->recv) {
die "Could not connect to i3"; die "Could not connect to i3";
} }
sub get_current_workspace {
my $current = first { $_->{focused} } @{$i3->get_workspaces->recv};
return $current->{name};
}
if (!defined($workspace) && !defined($output)) {
$workspace = get_current_workspace();
}
sub filter_containers { sub filter_containers {
my ($tree, $pred) = @_; my ($tree, $pred) = @_;
@ -216,7 +222,7 @@ my $tree = $i3->get_tree->recv;
my $dump; my $dump;
if (defined($workspace)) { if (defined($workspace)) {
$dump = filter_containers($tree, sub { $dump = filter_containers($tree, sub {
$_->{type} eq 'workspace' && $_->{name} eq $workspace $_->{type} eq 'workspace' && ($_->{name} eq $workspace || ($workspace =~ /^\d+$/ && $_->{num} eq $workspace))
}); });
} else { } else {
$dump = filter_containers($tree, sub { $dump = filter_containers($tree, sub {
@ -246,7 +252,7 @@ for my $key (qw(nodes floating_nodes)) {
=head1 SYNOPSIS =head1 SYNOPSIS
i3-save-tree [--workspace=name] [--output=name] i3-save-tree [--workspace=name|number] [--output=name]
=head1 DESCRIPTION =head1 DESCRIPTION
@ -259,19 +265,20 @@ specification. When a window is mapped (made visible on the screen) that
matches the specification, i3 will put it into that place and kill the matches the specification, i3 will put it into that place and kill the
placeholder. placeholder.
If neither argument is specified, the currently focused workspace will be used.
=head1 OPTIONS =head1 OPTIONS
=over =over
=item B<--workspace=name> =item B<--workspace=name|number>
Specifies the workspace that should be dumped, e.g. 1. Either this or --output Specifies the workspace that should be dumped, e.g. 1. This can either be a
need to be specified. name or the number of a workspace.
=item B<--output=name> =item B<--output=name>
Specifies the output that should be dumped, e.g. LVDS-1. Either this or Specifies the output that should be dumped, e.g. LVDS-1.
--workspace need to be specified.
=back =back

View File

@ -13,3 +13,5 @@ for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm
exec $terminal "$@" exec $terminal "$@"
fi fi
done done
i3-nagbar -m 'i3-sensible-terminal could not find a terminal emulator. Please install one.'

View File

@ -11,9 +11,12 @@
# Font for window titles. Will also be used by the bar unless a different font # Font for window titles. Will also be used by the bar unless a different font
# is used in the bar {} block below. # is used in the bar {} block below.
font pango:monospace 8
# This font is widely installed, provides lots of unicode glyphs, right-to-left # This font is widely installed, provides lots of unicode glyphs, right-to-left
# text rendering and scalability on retina/hidpi displays (thanks to pango). # text rendering and scalability on retina/hidpi displays (thanks to pango).
font pango:DejaVu Sans Mono 8 #font pango:DejaVu Sans Mono 8
# Before i3 v4.8, we used to recommend this one as the default: # Before i3 v4.8, we used to recommend this one as the default:
# font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# The font above is very space-efficient, that is, it looks good, sharp and # The font above is very space-efficient, that is, it looks good, sharp and
@ -166,7 +169,7 @@ bar {
####################################################################### #######################################################################
# automatically start i3-config-wizard to offer the user to create a # automatically start i3-config-wizard to offer the user to create a
# keysym-based config which used his favorite modifier (alt or windows) # keysym-based config which used their favorite modifier (alt or windows)
# #
# i3-config-wizard will not launch if there already is a config file # i3-config-wizard will not launch if there already is a config file
# in ~/.i3/config. # in ~/.i3/config.

View File

@ -12,9 +12,12 @@ set $mod Mod1
# Font for window titles. Will also be used by the bar unless a different font # Font for window titles. Will also be used by the bar unless a different font
# is used in the bar {} block below. # is used in the bar {} block below.
font pango:monospace 8
# This font is widely installed, provides lots of unicode glyphs, right-to-left # This font is widely installed, provides lots of unicode glyphs, right-to-left
# text rendering and scalability on retina/hidpi displays (thanks to pango). # text rendering and scalability on retina/hidpi displays (thanks to pango).
font pango:DejaVu Sans Mono 8 #font pango:DejaVu Sans Mono 8
# Before i3 v4.8, we used to recommend this one as the default: # Before i3 v4.8, we used to recommend this one as the default:
# font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# The font above is very space-efficient, that is, it looks good, sharp and # The font above is very space-efficient, that is, it looks good, sharp and

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* child.c: Getting Input for the statusline * child.c: Getting input for the statusline
* *
*/ */
#pragma once #pragma once
@ -41,7 +41,7 @@ typedef struct {
} i3bar_child; } i3bar_child;
/* /*
* Start a child-process with the specified command and reroute stdin. * Start a child process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care * We actually start a $SHELL to execute the command so we don't have to care
* about arguments and such * about arguments and such
* *
@ -49,26 +49,26 @@ typedef struct {
void start_child(char *command); void start_child(char *command);
/* /*
* kill()s the child-process (if any). Called when exit()ing. * kill()s the child process (if any). Called when exit()ing.
* *
*/ */
void kill_child_at_exit(void); void kill_child_at_exit(void);
/* /*
* kill()s the child-process (if any) and closes and * kill()s the child process (if any) and closes and
* free()s the stdin- and sigchild-watchers * free()s the stdin- and SIGCHLD-watchers
* *
*/ */
void kill_child(void); void kill_child(void);
/* /*
* Sends a SIGSTOP to the child-process (if existent) * Sends a SIGSTOP to the child process (if existent)
* *
*/ */
void stop_child(void); void stop_child(void);
/* /*
* Sends a SIGCONT to the child-process (if existent) * Sends a SIGCONT to the child process (if existent)
* *
*/ */
void cont_child(void); void cont_child(void);

View File

@ -16,8 +16,6 @@
typedef struct rect_t rect; typedef struct rect_t rect;
struct ev_loop *main_loop; struct ev_loop *main_loop;
char *statusline;
char *statusline_buffer;
struct rect_t { struct rect_t {
int x; int x;
@ -37,13 +35,21 @@ typedef enum {
* up one status line. */ * up one status line. */
struct status_block { struct status_block {
i3String *full_text; i3String *full_text;
i3String *short_text;
char *color; char *color;
/* min_width can be specified either as a numeric value (in pixels) or as a
* string. For strings, we set min_width to the measured text width of
* min_width_str. */
uint32_t min_width; uint32_t min_width;
char *min_width_str;
blockalign_t align; blockalign_t align;
bool urgent; bool urgent;
bool no_separator; bool no_separator;
bool is_markup;
/* The amount of pixels necessary to render a separater after the block. */ /* The amount of pixels necessary to render a separater after the block. */
uint32_t sep_block_width; uint32_t sep_block_width;

View File

@ -35,6 +35,7 @@ typedef struct config_t {
char *bar_id; char *bar_id;
char *command; char *command;
char *fontname; char *fontname;
i3String *separator_symbol;
char *tray_output; char *tray_output;
int num_outputs; int num_outputs;
char **outputs; char **outputs;
@ -49,7 +50,7 @@ typedef struct config_t {
config_t config; config_t config;
/** /**
* Start parsing the received bar configuration json-string * Start parsing the received bar configuration JSON string
* *
*/ */
void parse_config_json(char *json); void parse_config_json(char *json);

View File

@ -13,7 +13,7 @@
/* /*
* Initiate a connection to i3. * Initiate a connection to i3.
* socket-path must be a valid path to the ipc_socket of i3 * socket_path must be a valid path to the ipc_socket of i3
* *
*/ */
int init_connection(const char *socket_path); int init_connection(const char *socket_path);
@ -25,7 +25,7 @@ int init_connection(const char *socket_path);
void destroy_connection(void); void destroy_connection(void);
/* /*
* Sends a Message to i3. * Sends a message to i3.
* type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information) * type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information)
* *
*/ */

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* mode.c: Handle mode-event and show current binding mode in the bar * mode.c: Handle "mode" event and show current binding mode in the bar
* *
*/ */
#pragma once #pragma once
@ -22,7 +22,7 @@ struct mode {
typedef struct mode mode; typedef struct mode mode;
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_mode_json(char *json); void parse_mode_json(char *json);

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* outputs.c: Maintaining the output-list * outputs.c: Maintaining the outputs list
* *
*/ */
#pragma once #pragma once
@ -19,13 +19,13 @@ SLIST_HEAD(outputs_head, i3_output);
struct outputs_head* outputs; struct outputs_head* outputs;
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_outputs_json(char* json); void parse_outputs_json(char* json);
/* /*
* Initiate the output-list * Initiate the outputs list
* *
*/ */
void init_outputs(void); void init_outputs(void);
@ -42,7 +42,7 @@ struct i3_output {
bool primary; /* If it is the primary output */ bool primary; /* If it is the primary output */
bool visible; /* If the bar is visible on this output */ bool visible; /* If the bar is visible on this output */
int ws; /* The number of the currently visible ws */ int ws; /* The number of the currently visible ws */
rect rect; /* The rect (relative to the root-win) */ rect rect; /* The rect (relative to the root window) */
xcb_window_t bar; /* The id of the bar of the output */ xcb_window_t bar; /* The id of the bar of the output */
xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */

View File

@ -26,7 +26,7 @@
} \ } \
} while (0) } while (0)
/* Securely fee single-linked list */ /* Securely free single-linked list */
#define FREE_SLIST(l, type) \ #define FREE_SLIST(l, type) \
do { \ do { \
type *walk = SLIST_FIRST(l); \ type *walk = SLIST_FIRST(l); \
@ -37,7 +37,7 @@
} \ } \
} while (0) } while (0)
/* Securely fee tail-queues */ /* Securely free tail queue */
#define FREE_TAILQ(l, type) \ #define FREE_TAILQ(l, type) \
do { \ do { \
type *walk = TAILQ_FIRST(l); \ type *walk = TAILQ_FIRST(l); \
@ -51,7 +51,7 @@
#if defined(DLOG) #if defined(DLOG)
#undef DLOG #undef DLOG
#endif #endif
/* Use cool logging-macros */ /* Use cool logging macros */
#define DLOG(fmt, ...) \ #define DLOG(fmt, ...) \
do { \ do { \
if (config.verbose) { \ if (config.verbose) { \

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* workspaces.c: Maintaining the workspace-lists * workspaces.c: Maintaining the workspace lists
* *
*/ */
#pragma once #pragma once
@ -18,13 +18,13 @@ typedef struct i3_ws i3_ws;
TAILQ_HEAD(ws_head, i3_ws); TAILQ_HEAD(ws_head, i3_ws);
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_workspaces_json(char *json); void parse_workspaces_json(char *json);
/* /*
* free() all workspace data-structures * free() all workspace data structures
* *
*/ */
void free_workspaces(void); void free_workspaces(void);
@ -36,7 +36,7 @@ struct i3_ws {
int name_width; /* The rendered width of the name */ int name_width; /* The rendered width of the name */
bool visible; /* If the ws is currently visible on an output */ bool visible; /* If the ws is currently visible on an output */
bool focused; /* If the ws is currently focused */ bool focused; /* If the ws is currently focused */
bool urgent; /* If the urgent-hint of the ws is set */ bool urgent; /* If the urgent hint of the ws is set */
rect rect; /* The rect of the ws (not used (yet)) */ rect rect; /* The rect of the ws (not used (yet)) */
struct i3_output *output; /* The current output of the ws */ struct i3_output *output; /* The current output of the ws */

View File

@ -65,14 +65,14 @@ void init_xcb_late(char *fontname);
void init_colors(const struct xcb_color_strings_t *colors); void init_colors(const struct xcb_color_strings_t *colors);
/* /*
* Cleanup the xcb-stuff. * Cleanup the xcb stuff.
* Called once, before the program terminates. * Called once, before the program terminates.
* *
*/ */
void clean_xcb(void); void clean_xcb(void);
/* /*
* Get the earlier requested atoms and save them in the prepared data-structure * Get the earlier requested atoms and save them in the prepared data structure
* *
*/ */
void get_atoms(void); void get_atoms(void);
@ -90,7 +90,7 @@ void kick_tray_clients(i3_output *output);
/* /*
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window * We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset, * to make GTK+ 3 applets with symbolic icons visible. If the colors are unset,
* they assume a light background. * they assume a light background.
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591 * See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
* *
@ -104,7 +104,7 @@ void init_tray_colors(void);
void destroy_window(i3_output *output); void destroy_window(i3_output *output);
/* /*
* Reallocate the statusline-buffer * Reallocate the statusline buffer
* *
*/ */
void realloc_sl_buffer(void); void realloc_sl_buffer(void);

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* child.c: Getting Input for the statusline * child.c: Getting input for the statusline
* *
*/ */
#include <stdlib.h> #include <stdlib.h>
@ -30,7 +30,7 @@
/* Global variables for child_*() */ /* Global variables for child_*() */
i3bar_child child; i3bar_child child;
/* stdin- and sigchild-watchers */ /* stdin- and SIGCHLD-watchers */
ev_io *stdin_io; ev_io *stdin_io;
ev_child *child_sig; ev_child *child_sig;
@ -54,26 +54,43 @@ typedef struct parser_ctx {
parser_ctx parser_context; parser_ctx parser_context;
/* The buffer statusline points to */
struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head); struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
char *statusline_buffer = NULL; /* Used temporarily while reading a statusline */
struct statusline_head statusline_buffer = TAILQ_HEAD_INITIALIZER(statusline_buffer);
int child_stdin; int child_stdin;
/* /*
* Clears all blocks from the statusline structure in memory and frees their * Remove all blocks from the given statusline.
* associated resources. * If free_resources is set, the fields of each status block will be free'd.
*/ */
static void clear_status_blocks() { static void clear_statusline(struct statusline_head *head, bool free_resources) {
struct status_block *first; struct status_block *first;
while (!TAILQ_EMPTY(&statusline_head)) { while (!TAILQ_EMPTY(head)) {
first = TAILQ_FIRST(&statusline_head); first = TAILQ_FIRST(head);
if (free_resources) {
I3STRING_FREE(first->full_text); I3STRING_FREE(first->full_text);
TAILQ_REMOVE(&statusline_head, first, blocks); I3STRING_FREE(first->short_text);
FREE(first->color);
FREE(first->name);
FREE(first->instance);
FREE(first->min_width_str);
}
TAILQ_REMOVE(head, first, blocks);
free(first); free(first);
} }
} }
static void copy_statusline(struct statusline_head *from, struct statusline_head *to) {
struct status_block *current;
TAILQ_FOREACH(current, from, blocks) {
struct status_block *new_block = smalloc(sizeof(struct status_block));
memcpy(new_block, current, sizeof(struct status_block));
TAILQ_INSERT_TAIL(to, new_block, blocks);
}
}
/* /*
* Replaces the statusline in memory with an error message. Pass a format * Replaces the statusline in memory with an error message. Pass a format
* string and format parameters as you would in `printf'. The next time * string and format parameters as you would in `printf'. The next time
@ -81,12 +98,12 @@ static void clear_status_blocks() {
* the space allocated for the statusline. * the space allocated for the statusline.
*/ */
__attribute__((format(printf, 1, 2))) static void set_statusline_error(const char *format, ...) { __attribute__((format(printf, 1, 2))) static void set_statusline_error(const char *format, ...) {
clear_status_blocks(); clear_statusline(&statusline_head, true);
char *message; char *message;
va_list args; va_list args;
va_start(args, format); va_start(args, format);
vasprintf(&message, format, args); (void)vasprintf(&message, format, args);
struct status_block *err_block = scalloc(sizeof(struct status_block)); struct status_block *err_block = scalloc(sizeof(struct status_block));
err_block->full_text = i3string_from_utf8("Error: "); err_block->full_text = i3string_from_utf8("Error: ");
@ -108,16 +125,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
} }
/* /*
* Stop and free() the stdin- and sigchild-watchers * Stop and free() the stdin- and SIGCHLD-watchers
* *
*/ */
void cleanup(void) { void cleanup(void) {
if (stdin_io != NULL) { if (stdin_io != NULL) {
ev_io_stop(main_loop, stdin_io); ev_io_stop(main_loop, stdin_io);
FREE(stdin_io); FREE(stdin_io);
FREE(statusline_buffer);
/* statusline pointed to memory within statusline_buffer */
statusline = NULL;
} }
if (child_sig != NULL) { if (child_sig != NULL) {
@ -130,20 +144,12 @@ void cleanup(void) {
/* /*
* The start of a new array is the start of a new status line, so we clear all * The start of a new array is the start of a new status line, so we clear all
* previous entries. * previous entries from the buffer.
*
*/ */
static int stdin_start_array(void *context) { static int stdin_start_array(void *context) {
struct status_block *first; // the blocks are still used by statusline_head, so we won't free the
while (!TAILQ_EMPTY(&statusline_head)) { // resources here.
first = TAILQ_FIRST(&statusline_head); clear_statusline(&statusline_buffer, false);
I3STRING_FREE(first->full_text);
FREE(first->color);
FREE(first->name);
FREE(first->instance);
TAILQ_REMOVE(&statusline_head, first, blocks);
free(first);
}
return 1; return 1;
} }
@ -172,10 +178,13 @@ static int stdin_boolean(void *context, int val) {
parser_ctx *ctx = context; parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "urgent") == 0) { if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
ctx->block.urgent = val; ctx->block.urgent = val;
return 1;
} }
if (strcasecmp(ctx->last_map_key, "separator") == 0) { if (strcasecmp(ctx->last_map_key, "separator") == 0) {
ctx->block.no_separator = !val; ctx->block.no_separator = !val;
return 1;
} }
return 1; return 1;
} }
@ -183,9 +192,19 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
parser_ctx *ctx = context; parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "full_text") == 0) { if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len); ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len);
return 1;
}
if (strcasecmp(ctx->last_map_key, "short_text") == 0) {
ctx->block.short_text = i3string_from_markup_with_length((const char *)val, len);
return 1;
} }
if (strcasecmp(ctx->last_map_key, "color") == 0) { if (strcasecmp(ctx->last_map_key, "color") == 0) {
sasprintf(&(ctx->block.color), "%.*s", len, val); sasprintf(&(ctx->block.color), "%.*s", len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "markup") == 0) {
ctx->block.is_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango")));
return 1;
} }
if (strcasecmp(ctx->last_map_key, "align") == 0) { if (strcasecmp(ctx->last_map_key, "align") == 0) {
if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) { if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) {
@ -195,23 +214,30 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
} else { } else {
ctx->block.align = ALIGN_LEFT; ctx->block.align = ALIGN_LEFT;
} }
} else if (strcasecmp(ctx->last_map_key, "min_width") == 0) { return 1;
i3String *text = i3string_from_markup_with_length((const char *)val, len); }
ctx->block.min_width = (uint32_t)predict_text_width(text); if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
i3string_free(text); char *copy = (char *)malloc(len + 1);
strncpy(copy, (const char *)val, len);
copy[len] = 0;
ctx->block.min_width_str = copy;
return 1;
} }
if (strcasecmp(ctx->last_map_key, "name") == 0) { if (strcasecmp(ctx->last_map_key, "name") == 0) {
char *copy = (char *)malloc(len + 1); char *copy = (char *)malloc(len + 1);
strncpy(copy, (const char *)val, len); strncpy(copy, (const char *)val, len);
copy[len] = 0; copy[len] = 0;
ctx->block.name = copy; ctx->block.name = copy;
return 1;
} }
if (strcasecmp(ctx->last_map_key, "instance") == 0) { if (strcasecmp(ctx->last_map_key, "instance") == 0) {
char *copy = (char *)malloc(len + 1); char *copy = (char *)malloc(len + 1);
strncpy(copy, (const char *)val, len); strncpy(copy, (const char *)val, len);
copy[len] = 0; copy[len] = 0;
ctx->block.instance = copy; ctx->block.instance = copy;
return 1;
} }
return 1; return 1;
} }
@ -219,13 +245,20 @@ static int stdin_integer(void *context, long long val) {
parser_ctx *ctx = context; parser_ctx *ctx = context;
if (strcasecmp(ctx->last_map_key, "min_width") == 0) { if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
ctx->block.min_width = (uint32_t)val; ctx->block.min_width = (uint32_t)val;
return 1;
} }
if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) { if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
ctx->block.sep_block_width = (uint32_t)val; ctx->block.sep_block_width = (uint32_t)val;
return 1;
} }
return 1; return 1;
} }
/*
* When a map is finished, we have an entire status block.
* Move it from the parser's context to the statusline buffer.
*/
static int stdin_end_map(void *context) { static int stdin_end_map(void *context) {
parser_ctx *ctx = context; parser_ctx *ctx = context;
struct status_block *new_block = smalloc(sizeof(struct status_block)); struct status_block *new_block = smalloc(sizeof(struct status_block));
@ -236,15 +269,37 @@ static int stdin_end_map(void *context) {
new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!"); new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!");
if (new_block->urgent) if (new_block->urgent)
ctx->has_urgent = true; ctx->has_urgent = true;
TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
if (new_block->min_width_str) {
i3String *text = i3string_from_utf8(new_block->min_width_str);
i3string_set_markup(text, new_block->is_markup);
new_block->min_width = (uint32_t)predict_text_width(text);
i3string_free(text);
}
i3string_set_markup(new_block->full_text, new_block->is_markup);
if (new_block->short_text != NULL)
i3string_set_markup(new_block->short_text, new_block->is_markup);
TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks);
return 1; return 1;
} }
/*
* When an array is finished, we have an entire statusline.
* Copy it from the buffer to the actual statusline.
*/
static int stdin_end_array(void *context) { static int stdin_end_array(void *context) {
DLOG("copying statusline_buffer to statusline_head\n");
clear_statusline(&statusline_head, true);
copy_statusline(&statusline_buffer, &statusline_head);
DLOG("dumping statusline:\n"); DLOG("dumping statusline:\n");
struct status_block *current; struct status_block *current;
TAILQ_FOREACH(current, &statusline_head, blocks) { TAILQ_FOREACH(current, &statusline_head, blocks) {
DLOG("full_text = %s\n", i3string_as_utf8(current->full_text)); DLOG("full_text = %s\n", i3string_as_utf8(current->full_text));
DLOG("short_text = %s\n", (current->short_text == NULL ? NULL : i3string_as_utf8(current->short_text)));
DLOG("color = %s\n", current->color); DLOG("color = %s\n", current->color);
} }
DLOG("end of dump\n"); DLOG("end of dump\n");
@ -386,8 +441,8 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
} }
/* /*
* We received a sigchild, meaning, that the child-process terminated. * We received a SIGCHLD, meaning, that the child process terminated.
* We simply free the respective data-structures and don't care for input * We simply free the respective data structures and don't care for input
* anymore * anymore
* *
*/ */
@ -415,16 +470,27 @@ void child_write_output(void) {
if (child.click_events) { if (child.click_events) {
const unsigned char *output; const unsigned char *output;
size_t size; size_t size;
ssize_t n;
yajl_gen_get_buf(gen, &output, &size); yajl_gen_get_buf(gen, &output, &size);
write(child_stdin, output, size);
write(child_stdin, "\n", 1); n = writeall(child_stdin, output, size);
if (n != -1)
n = writeall(child_stdin, "\n", 1);
yajl_gen_clear(gen); yajl_gen_clear(gen);
if (n == -1) {
child.click_events = false;
kill_child();
set_statusline_error("child_write_output failed");
draw_bars(false);
}
} }
} }
/* /*
* Start a child-process with the specified command and reroute stdin. * Start a child process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care * We actually start a $SHELL to execute the command so we don't have to care
* about arguments and such. * about arguments and such.
* *
@ -552,7 +618,7 @@ void send_block_clicked(int button, const char *name, const char *instance, int
} }
/* /*
* kill()s the child-process (if any). Called when exit()ing. * kill()s the child process (if any). Called when exit()ing.
* *
*/ */
void kill_child_at_exit(void) { void kill_child_at_exit(void) {
@ -564,8 +630,8 @@ void kill_child_at_exit(void) {
} }
/* /*
* kill()s the child-process (if existent) and closes and * kill()s the child process (if existent) and closes and
* free()s the stdin- and sigchild-watchers * free()s the stdin- and SIGCHLD-watchers
* *
*/ */
void kill_child(void) { void kill_child(void) {
@ -580,7 +646,7 @@ void kill_child(void) {
} }
/* /*
* Sends a SIGSTOP to the child-process (if existent) * Sends a SIGSTOP to the child process (if existent)
* *
*/ */
void stop_child(void) { void stop_child(void) {
@ -591,7 +657,7 @@ void stop_child(void) {
} }
/* /*
* Sends a SIGCONT to the child-process (if existent) * Sends a SIGCONT to the child process (if existent)
* *
*/ */
void cont_child(void) { void cont_child(void) {

View File

@ -38,7 +38,7 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
} }
/* /*
* Parse a null-value (current_workspace) * Parse a null value (current_workspace)
* *
*/ */
static int config_null_cb(void *params_) { static int config_null_cb(void *params_) {
@ -144,6 +144,13 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
return 1; return 1;
} }
if (!strcmp(cur_key, "separator_symbol")) {
DLOG("separator = %.*s\n", len, val);
I3STRING_FREE(config.separator_symbol);
config.separator_symbol = i3string_from_utf8_with_length((const char *)val, len);
return 1;
}
if (!strcmp(cur_key, "outputs")) { if (!strcmp(cur_key, "outputs")) {
DLOG("+output %.*s\n", len, val); DLOG("+output %.*s\n", len, val);
int new_num_outputs = config.num_outputs + 1; int new_num_outputs = config.num_outputs + 1;
@ -231,7 +238,7 @@ static yajl_callbacks outputs_callbacks = {
}; };
/* /*
* Start parsing the received bar configuration json-string * Start parsing the received bar configuration JSON string
* *
*/ */
void parse_config_json(char *json) { void parse_config_json(char *json) {
@ -241,13 +248,13 @@ void parse_config_json(char *json) {
state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Proper errorhandling for JSON-parsing */ /* FIXME: Proper error handling for JSON parsing */
switch (state) { switch (state) {
case yajl_status_ok: case yajl_status_ok:
break; break;
case yajl_status_client_canceled: case yajl_status_client_canceled:
case yajl_status_error: case yajl_status_error:
ELOG("Could not parse config-reply!\n"); ELOG("Could not parse config reply!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
break; break;
} }

View File

@ -32,15 +32,15 @@ typedef void (*handler_t)(char *);
* *
*/ */
void got_command_reply(char *reply) { void got_command_reply(char *reply) {
/* TODO: Error handling for command-replies */ /* TODO: Error handling for command replies */
} }
/* /*
* Called, when we get a reply with workspaces-data * Called, when we get a reply with workspaces data
* *
*/ */
void got_workspace_reply(char *reply) { void got_workspace_reply(char *reply) {
DLOG("Got Workspace-Data!\n"); DLOG("Got workspace data!\n");
parse_workspaces_json(reply); parse_workspaces_json(reply);
draw_bars(false); draw_bars(false);
} }
@ -51,18 +51,18 @@ void got_workspace_reply(char *reply) {
* *
*/ */
void got_subscribe_reply(char *reply) { void got_subscribe_reply(char *reply) {
DLOG("Got Subscribe Reply: %s\n", reply); DLOG("Got subscribe reply: %s\n", reply);
/* TODO: Error handling for subscribe-commands */ /* TODO: Error handling for subscribe commands */
} }
/* /*
* Called, when we get a reply with outputs-data * Called, when we get a reply with outputs data
* *
*/ */
void got_output_reply(char *reply) { void got_output_reply(char *reply) {
DLOG("Parsing Outputs-JSON...\n"); DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply); parse_outputs_json(reply);
DLOG("Reconfiguring Windows...\n"); DLOG("Reconfiguring windows...\n");
realloc_sl_buffer(); realloc_sl_buffer();
reconfig_windows(false); reconfig_windows(false);
@ -80,7 +80,7 @@ void got_output_reply(char *reply) {
*/ */
void got_bar_config(char *reply) { void got_bar_config(char *reply) {
DLOG("Received bar config \"%s\"\n", reply); DLOG("Received bar config \"%s\"\n", reply);
/* We initiate the main-function by requesting infos about the outputs and /* We initiate the main function by requesting infos about the outputs and
* workspaces. Everything else (creating the bars, showing the right workspace- * workspaces. Everything else (creating the bars, showing the right workspace-
* buttons and more) is taken care of by the event-drivenness of the code */ * buttons and more) is taken care of by the event-drivenness of the code */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
@ -104,7 +104,7 @@ void got_bar_config(char *reply) {
FREE(config.command); FREE(config.command);
} }
/* Data-structure to easily call the reply-handlers later */ /* Data structure to easily call the reply handlers later */
handler_t reply_handlers[] = { handler_t reply_handlers[] = {
&got_command_reply, &got_command_reply,
&got_workspace_reply, &got_workspace_reply,
@ -116,20 +116,20 @@ handler_t reply_handlers[] = {
}; };
/* /*
* Called, when a workspace-event arrives (i.e. the user changed the workspace) * Called, when a workspace event arrives (i.e. the user changed the workspace)
* *
*/ */
void got_workspace_event(char *event) { void got_workspace_event(char *event) {
DLOG("Got Workspace Event!\n"); DLOG("Got workspace event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
} }
/* /*
* Called, when an output-event arrives (i.e. the screen-configuration changed) * Called, when an output event arrives (i.e. the screen configuration changed)
* *
*/ */
void got_output_event(char *event) { void got_output_event(char *event) {
DLOG("Got Output Event!\n"); DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
if (!config.disable_ws) { if (!config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
@ -137,11 +137,11 @@ void got_output_event(char *event) {
} }
/* /*
* Called, when a mode-event arrives (i3 changed binding mode). * Called, when a mode event arrives (i3 changed binding mode).
* *
*/ */
void got_mode_event(char *event) { void got_mode_event(char *event) {
DLOG("Got Mode Event!\n"); DLOG("Got mode event!\n");
parse_mode_json(event); parse_mode_json(event);
draw_bars(false); draw_bars(false);
} }
@ -180,7 +180,7 @@ void got_bar_config_update(char *event) {
draw_bars(false); draw_bars(false);
} }
/* Data-structure to easily call the event-handlers later */ /* Data structure to easily call the event handlers later */
handler_t event_handlers[] = { handler_t event_handlers[] = {
&got_workspace_event, &got_workspace_event,
&got_output_event, &got_output_event,
@ -201,7 +201,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) * 2; uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) * 2;
char *header = smalloc(header_len); char *header = smalloc(header_len);
/* We first parse the fixed-length IPC-header, to know, how much data /* We first parse the fixed-length IPC header, to know, how much data
* we have to expect */ * we have to expect */
uint32_t rec = 0; uint32_t rec = 0;
while (rec < header_len) { while (rec < header_len) {
@ -268,7 +268,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
} }
/* /*
* Sends a Message to i3. * Sends a message to i3.
* type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information) * type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information)
* *
*/ */
@ -296,18 +296,7 @@ int i3_send_msg(uint32_t type, const char *payload) {
if (payload != NULL) if (payload != NULL)
strncpy(walk, payload, len); strncpy(walk, payload, len);
uint32_t written = 0; swrite(i3_connection->fd, buffer, to_write);
while (to_write > 0) {
int n = write(i3_connection->fd, buffer + written, to_write);
if (n == -1) {
ELOG("write() failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
to_write -= n;
written += n;
}
FREE(buffer); FREE(buffer);
@ -316,7 +305,7 @@ int i3_send_msg(uint32_t type, const char *payload) {
/* /*
* Initiate a connection to i3. * Initiate a connection to i3.
* socket-path must be a valid path to the ipc_socket of i3 * socket_path must be a valid path to the ipc_socket of i3
* *
*/ */
int init_connection(const char *socket_path) { int init_connection(const char *socket_path) {

View File

@ -60,7 +60,7 @@ void print_usage(char *elf_name) {
printf("\n"); printf("\n");
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n"); printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n"); printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
printf("-h, --help Display this help-message and exit\n"); printf("-h, --help Display this help message and exit\n");
printf("-v, --version Display version number and exit\n"); printf("-v, --version Display version number and exit\n");
printf("\n"); printf("\n");
printf(" PLEASE NOTE that i3bar will be automatically started by i3\n" printf(" PLEASE NOTE that i3bar will be automatically started by i3\n"
@ -71,7 +71,7 @@ void print_usage(char *elf_name) {
/* /*
* We watch various signals, that are there to make our application stop. * We watch various signals, that are there to make our application stop.
* If we get one of those, we ev_unloop() and invoke the cleanup-routines * If we get one of those, we ev_unloop() and invoke the cleanup routines
* in main() with that * in main() with that
* *
*/ */
@ -140,7 +140,7 @@ int main(int argc, char **argv) {
} }
if (socket_path == NULL) { if (socket_path == NULL) {
ELOG("No Socket Path Specified, default to %s\n", i3_default_sock_path); ELOG("No socket path specified, default to %s\n", i3_default_sock_path);
socket_path = expand_path(i3_default_sock_path); socket_path = expand_path(i3_default_sock_path);
} }
@ -150,7 +150,7 @@ int main(int argc, char **argv) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id); i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
} }
/* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main-loop. /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop.
* We only need those watchers on the stack, so putting them on the stack saves us * We only need those watchers on the stack, so putting them on the stack saves us
* some calls to free() */ * some calls to free() */
ev_signal *sig_term = smalloc(sizeof(ev_signal)); ev_signal *sig_term = smalloc(sizeof(ev_signal));
@ -166,13 +166,11 @@ int main(int argc, char **argv) {
ev_signal_start(main_loop, sig_hup); ev_signal_start(main_loop, sig_hup);
/* From here on everything should run smooth for itself, just start listening for /* From here on everything should run smooth for itself, just start listening for
* events. We stop simply stop the event-loop, when we are finished */ * events. We stop simply stop the event loop, when we are finished */
ev_loop(main_loop, 0); ev_loop(main_loop, 0);
kill_child(); kill_child();
FREE(statusline_buffer);
clean_xcb(); clean_xcb();
ev_default_destroy(); ev_default_destroy();

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* mode.c: Handle mode-event and show current binding mode in the bar * mode.c: Handle mode event and show current binding mode in the bar
* *
*/ */
#include <string.h> #include <string.h>
@ -48,7 +48,7 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
/* /*
* Parse a key. * Parse a key.
* *
* Essentially we just save it in the parsing-state * Essentially we just save it in the parsing state
* *
*/ */
static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
@ -69,11 +69,11 @@ static yajl_callbacks mode_callbacks = {
}; };
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_mode_json(char *json) { void parse_mode_json(char *json) {
/* FIXME: Fasciliate stream-processing, i.e. allow starting to interpret /* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */ * JSON in chunks */
struct mode_json_params params; struct mode_json_params params;
@ -90,13 +90,13 @@ void parse_mode_json(char *json) {
state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Propper errorhandling for JSON-parsing */ /* FIXME: Propper error handling for JSON parsing */
switch (state) { switch (state) {
case yajl_status_ok: case yajl_status_ok:
break; break;
case yajl_status_client_canceled: case yajl_status_client_canceled:
case yajl_status_error: case yajl_status_error:
ELOG("Could not parse mode-event!\n"); ELOG("Could not parse mode event!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
break; break;
} }

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* outputs.c: Maintaining the output-list * outputs.c: Maintaining the outputs list
* *
*/ */
#include <string.h> #include <string.h>
@ -27,7 +27,7 @@ struct outputs_json_params {
}; };
/* /*
* Parse a null-value (current_workspace) * Parse a null value (current_workspace)
* *
*/ */
static int outputs_null_cb(void *params_) { static int outputs_null_cb(void *params_) {
@ -139,7 +139,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
} }
/* /*
* We hit the start of a json-map (rect or a new output) * We hit the start of a JSON map (rect or a new output)
* *
*/ */
static int outputs_start_map_cb(void *params_) { static int outputs_start_map_cb(void *params_) {
@ -221,7 +221,7 @@ static int outputs_end_map_cb(void *params_) {
/* /*
* Parse a key. * Parse a key.
* *
* Essentially we just save it in the parsing-state * Essentially we just save it in the parsing state
* *
*/ */
static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
@ -247,7 +247,7 @@ static yajl_callbacks outputs_callbacks = {
}; };
/* /*
* Initiate the output-list * Initiate the outputs list
* *
*/ */
void init_outputs(void) { void init_outputs(void) {
@ -256,7 +256,7 @@ void init_outputs(void) {
} }
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_outputs_json(char *json) { void parse_outputs_json(char *json) {
@ -279,7 +279,7 @@ void parse_outputs_json(char *json) {
break; break;
case yajl_status_client_canceled: case yajl_status_client_canceled:
case yajl_status_error: case yajl_status_error:
ELOG("Could not parse outputs-reply!\n"); ELOG("Could not parse outputs reply!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
break; break;
} }

View File

@ -4,7 +4,7 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* workspaces.c: Maintaining the workspace-lists * workspaces.c: Maintaining the workspace lists
* *
*/ */
#include <string.h> #include <string.h>
@ -135,7 +135,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
params->workspaces_walk->name_width = params->workspaces_walk->name_width =
predict_text_width(params->workspaces_walk->name); predict_text_width(params->workspaces_walk->name);
DLOG("Got Workspace canonical: %s, name: '%s', name_width: %d, glyphs: %zu\n", DLOG("Got workspace canonical: %s, name: '%s', name_width: %d, glyphs: %zu\n",
params->workspaces_walk->canonical_name, params->workspaces_walk->canonical_name,
i3string_as_utf8(params->workspaces_walk->name), i3string_as_utf8(params->workspaces_walk->name),
params->workspaces_walk->name_width, params->workspaces_walk->name_width,
@ -167,7 +167,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
} }
/* /*
* We hit the start of a json-map (rect or a new output) * We hit the start of a JSON map (rect or a new output)
* *
*/ */
static int workspaces_start_map_cb(void *params_) { static int workspaces_start_map_cb(void *params_) {
@ -195,7 +195,7 @@ static int workspaces_start_map_cb(void *params_) {
/* /*
* Parse a key. * Parse a key.
* *
* Essentially we just save it in the parsing-state * Essentially we just save it in the parsing state
* *
*/ */
static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
@ -219,11 +219,11 @@ static yajl_callbacks workspaces_callbacks = {
}; };
/* /*
* Start parsing the received json-string * Start parsing the received JSON string
* *
*/ */
void parse_workspaces_json(char *json) { void parse_workspaces_json(char *json) {
/* FIXME: Fasciliate stream-processing, i.e. allow starting to interpret /* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */ * JSON in chunks */
struct workspaces_json_params params; struct workspaces_json_params params;
@ -239,13 +239,13 @@ void parse_workspaces_json(char *json) {
state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Propper errorhandling for JSON-parsing */ /* FIXME: Propper error handling for JSON parsing */
switch (state) { switch (state) {
case yajl_status_ok: case yajl_status_ok:
break; break;
case yajl_status_client_canceled: case yajl_status_client_canceled:
case yajl_status_error: case yajl_status_error:
ELOG("Could not parse workspaces-reply!\n"); ELOG("Could not parse workspaces reply!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
break; break;
} }
@ -256,7 +256,7 @@ void parse_workspaces_json(char *json) {
} }
/* /*
* free() all workspace data-structures. Does not free() the heads of the tailqueues. * free() all workspace data structures. Does not free() the heads of the tailqueues.
* *
*/ */
void free_workspaces(void) { void free_workspaces(void) {

View File

@ -34,7 +34,7 @@
#include "common.h" #include "common.h"
#include "libi3.h" #include "libi3.h"
/* We save the Atoms in an easy to access array, indexed by an enum */ /* We save the atoms in an easy to access array, indexed by an enum */
enum { enum {
#define ATOM_DO(name) name, #define ATOM_DO(name) name,
#include "xcb_atoms.def" #include "xcb_atoms.def"
@ -63,6 +63,9 @@ static i3Font font;
/* Overall height of the bar (based on font size) */ /* Overall height of the bar (based on font size) */
int bar_height; int bar_height;
/* Cached width of the custom separator if one was set */
int separator_symbol_width;
/* These are only relevant for XKB, which we only need for grabbing modifiers */ /* These are only relevant for XKB, which we only need for grabbing modifiers */
int xkb_base; int xkb_base;
int mod_pressed = 0; int mod_pressed = 0;
@ -74,7 +77,7 @@ xcb_gcontext_t statusline_clear;
xcb_pixmap_t statusline_pm; xcb_pixmap_t statusline_pm;
uint32_t statusline_width; uint32_t statusline_width;
/* Event-Watchers, to interact with the user */ /* Event watchers, to interact with the user */
ev_prepare *xcb_prep; ev_prepare *xcb_prep;
ev_check *xcb_chk; ev_check *xcb_chk;
ev_io *xcb_io; ev_io *xcb_io;
@ -106,7 +109,26 @@ struct xcb_colors_t {
}; };
struct xcb_colors_t colors; struct xcb_colors_t colors;
/* We define xcb_request_failed as a macro to include the relevant line-number */ /* Horizontal offset between a workspace label and button borders */
static const int ws_hoff_px = 4;
/* Vertical offset between a workspace label and button borders */
static const int ws_voff_px = 3;
/* Offset between two workspace buttons */
static const int ws_spacing_px = 1;
/* Offset between the statusline and 1) workspace buttons on the left
* 2) the tray or screen edge on the right */
static const int sb_hoff_px = 4;
/* Additional offset between the tray and the statusline, if the tray is not empty */
static const int tray_loff_px = 2;
/* Vertical offset between the bar and a separator */
static const int sep_voff_px = 4;
/* We define xcb_request_failed as a macro to include the relevant line number */
#define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__) #define xcb_request_failed(cookie, err_msg) _xcb_request_failed(cookie, err_msg, __LINE__)
int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) { int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
xcb_generic_error_t *err; xcb_generic_error_t *err;
@ -123,11 +145,51 @@ uint32_t get_sep_offset(struct status_block *block) {
return 0; return 0;
} }
int get_tray_width(struct tc_head *trayclients) {
trayclient *trayclient;
int tray_width = 0;
TAILQ_FOREACH_REVERSE(trayclient, trayclients, tc_head, tailq) {
if (!trayclient->mapped)
continue;
tray_width += font.height + logical_px(2);
}
if (tray_width > 0)
tray_width += logical_px(tray_loff_px);
return tray_width;
}
/*
* Draws a separator for the given block if necessary.
*
*/
static void draw_separator(uint32_t x, struct status_block *block) {
uint32_t sep_offset = get_sep_offset(block);
if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0)
return;
uint32_t center_x = x - sep_offset;
if (config.separator_symbol == NULL) {
/* Draw a classic one pixel, vertical separator. */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH;
uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)};
xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm, statusline_ctx, 2,
(xcb_point_t[]){{center_x, logical_px(sep_voff_px)},
{center_x, bar_height - logical_px(sep_voff_px)}});
} else {
/* Draw a custom separator. */
uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
set_font_colors(statusline_ctx, colors.sep_fg, colors.bar_bg);
draw_text(config.separator_symbol, statusline_pm, statusline_ctx,
separator_x, logical_px(ws_voff_px), x - separator_x);
}
}
/* /*
* Redraws the statusline to the buffer * Redraws the statusline to the buffer
* *
*/ */
void refresh_statusline(void) { void refresh_statusline(bool use_short_text) {
struct status_block *block; struct status_block *block;
uint32_t old_statusline_width = statusline_width; uint32_t old_statusline_width = statusline_width;
@ -135,6 +197,12 @@ void refresh_statusline(void) {
/* Predict the text width of all blocks (in pixels). */ /* Predict the text width of all blocks (in pixels). */
TAILQ_FOREACH(block, &statusline_head, blocks) { TAILQ_FOREACH(block, &statusline_head, blocks) {
/* Try to use the shorter text if necessary and possible. */
if (use_short_text && block->short_text != NULL) {
I3STRING_FREE(block->full_text);
block->full_text = i3string_copy(block->short_text);
}
if (i3string_get_num_bytes(block->full_text) == 0) if (i3string_get_num_bytes(block->full_text) == 0)
continue; continue;
@ -174,7 +242,7 @@ void refresh_statusline(void) {
realloc_sl_buffer(); realloc_sl_buffer();
/* Clear the statusline pixmap. */ /* Clear the statusline pixmap. */
xcb_rectangle_t rect = {0, 0, root_screen->width_in_pixels, bar_height}; xcb_rectangle_t rect = {0, 0, MAX(root_screen->width_in_pixels, statusline_width), bar_height};
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect); xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
/* Draw the text of each block. */ /* Draw the text of each block. */
@ -192,32 +260,23 @@ void refresh_statusline(void) {
/* Draw the background */ /* Draw the background */
uint32_t bg_color = colors.urgent_ws_bg; uint32_t bg_color = colors.urgent_ws_bg;
uint32_t bg_values[] = { bg_color, bg_color }; uint32_t bg_values[] = {bg_color, bg_color};
xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values); xcb_change_gc(xcb_connection, statusline_ctx, mask, bg_values);
/* The urgent background “overshoots” by 2 px so that the text that /* The urgent background “overshoots” by 2 px so that the text that
* is printed onto it will not be look so cut off. */ * is printed onto it will not be look so cut off. */
xcb_rectangle_t bg_rect = { x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2) }; xcb_rectangle_t bg_rect = {x - logical_px(2), logical_px(1), block->width + logical_px(4), bar_height - logical_px(2)};
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect); xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_ctx, 1, &bg_rect);
} else { } else {
fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg); fg_color = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
} }
set_font_colors(statusline_ctx, fg_color, colors.bar_bg); set_font_colors(statusline_ctx, fg_color, colors.bar_bg);
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 3, block->width); draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, logical_px(ws_voff_px), block->width);
x += block->width + block->sep_block_width + block->x_offset + block->x_append; x += block->width + block->sep_block_width + block->x_offset + block->x_append;
uint32_t sep_offset = get_sep_offset(block); /* If this is not the last block, draw a separator. */
if (TAILQ_NEXT(block, blocks) != NULL && sep_offset > 0) { draw_separator(x, block);
/* This is not the last block, draw a separator. */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH;
uint32_t values[] = {colors.sep_fg, colors.bar_bg, logical_px(1)};
xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
statusline_ctx, 2,
(xcb_point_t[]) { { x - sep_offset, logical_px(4) },
{ x - sep_offset, bar_height - logical_px(4) } });
}
} }
} }
@ -273,7 +332,7 @@ void unhide_bars(void) {
values[2] = walk->rect.w; values[2] = walk->rect.w;
values[3] = bar_height; values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE; values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]); DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
cookie = xcb_configure_window_checked(xcb_connection, cookie = xcb_configure_window_checked(xcb_connection,
walk->bar, walk->bar,
mask, mask,
@ -317,14 +376,12 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
} }
/* /*
* Handle a button-press-event (i.e. a mouse click on one of our bars). * Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occured on a ws-button or if the scroll- * We determine, whether the click occured on a workspace button or if the scroll-
* wheel was used and change the workspace appropriately * wheel was used and change the workspace appropriately
* *
*/ */
void handle_button(xcb_button_press_event_t *event) { void handle_button(xcb_button_press_event_t *event) {
i3_ws *cur_ws;
/* Determine, which bar was clicked */ /* Determine, which bar was clicked */
i3_output *walk; i3_output *walk;
xcb_window_t bar = event->event; xcb_window_t bar = event->event;
@ -335,45 +392,47 @@ void handle_button(xcb_button_press_event_t *event) {
} }
if (walk == NULL) { if (walk == NULL) {
DLOG("Unknown Bar klicked!\n"); DLOG("Unknown bar clicked!\n");
return; return;
} }
int32_t x = event->event_x >= 0 ? event->event_x : 0; int32_t x = event->event_x >= 0 ? event->event_x : 0;
int32_t original_x = x; int32_t original_x = x;
DLOG("Got Button %d\n", event->detail); DLOG("Got button %d\n", event->detail);
if (child_want_click_events()) { int workspace_width = 0;
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) {
int w = 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
if (x >= workspace_width && x <= workspace_width + w)
clicked_ws = ws_walk;
if (ws_walk->visible)
cur_ws = ws_walk;
workspace_width += w;
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
workspace_width += logical_px(ws_spacing_px);
}
if (x > workspace_width && child_want_click_events()) {
/* If the child asked for click events, /* If the child asked for click events,
* check if a status block has been clicked. */ * check if a status block has been clicked. */
int tray_width = get_tray_width(walk->trayclients);
/* First calculate width of tray area */
trayclient *trayclient;
int tray_width = 0;
TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
if (!trayclient->mapped)
continue;
tray_width += (font.height + logical_px(2));
}
if (tray_width > 0)
tray_width += logical_px(2);
int block_x = 0, last_block_x; int block_x = 0, last_block_x;
int offset = walk->rect.w - statusline_width - tray_width - logical_px(4); int offset = walk->rect.w - statusline_width - tray_width - logical_px(sb_hoff_px);
x = original_x - offset; x = original_x - offset;
if (x >= 0) { if (x >= 0) {
struct status_block *block; struct status_block *block;
int sep_offset_remainder = 0; int sep_offset_remainder = 0;
TAILQ_FOREACH (block, &statusline_head, blocks) { TAILQ_FOREACH(block, &statusline_head, blocks) {
if (i3string_get_num_bytes(block->full_text) == 0) if (i3string_get_num_bytes(block->full_text) == 0)
continue; continue;
last_block_x = block_x; last_block_x = block_x;
block_x += block->width + block->x_offset + block->x_append block_x += block->width + block->x_offset + block->x_append + get_sep_offset(block) + sep_offset_remainder;
+ get_sep_offset(block) + sep_offset_remainder;
if (x <= block_x && x >= last_block_x) { if (x <= block_x && x >= last_block_x) {
send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y); send_block_clicked(event->detail, block->name, block->instance, event->root_x, event->root_y);
@ -386,15 +445,8 @@ void handle_button(xcb_button_press_event_t *event) {
x = original_x; x = original_x;
} }
/* TODO: Move this to extern get_ws_for_output() */
TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) {
if (cur_ws->visible) {
break;
}
}
if (cur_ws == NULL) { if (cur_ws == NULL) {
DLOG("No Workspace active?\n"); DLOG("No workspace active?\n");
return; return;
} }
@ -436,17 +488,10 @@ void handle_button(xcb_button_press_event_t *event) {
cur_ws = TAILQ_NEXT(cur_ws, tailq); cur_ws = TAILQ_NEXT(cur_ws, tailq);
break; break;
case 1: case 1:
/* Check if this event regards a workspace button */ cur_ws = clicked_ws;
TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
DLOG("x = %d\n", x);
if (x >= 0 && x < cur_ws->name_width + logical_px(10)) {
break;
}
x -= cur_ws->name_width + logical_px(11);
}
/* Otherwise, focus our currently visible workspace if it is not /* if no workspace was clicked, focus our currently visible
* already focused */ * workspace if it is not already focused */
if (cur_ws == NULL) { if (cur_ws == NULL) {
TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) { TAILQ_FOREACH(cur_ws, walk->workspaces, tailq) {
if (cur_ws->visible && !cur_ws->focused) if (cur_ws->visible && !cur_ws->focused)
@ -471,7 +516,7 @@ void handle_button(xcb_button_press_event_t *event) {
size_t namelen = 0; size_t namelen = 0;
const char *utf8_name = cur_ws->canonical_name; const char *utf8_name = cur_ws->canonical_name;
for (const char *walk = utf8_name; *walk != '\0'; walk++) { for (const char *walk = utf8_name; *walk != '\0'; walk++) {
if (*walk == '"') if (*walk == '"' || *walk == '\\')
num_quotes++; num_quotes++;
/* While were looping through the name anyway, we can save one /* While were looping through the name anyway, we can save one
* strlen(). */ * strlen(). */
@ -485,7 +530,7 @@ void handle_button(xcb_button_press_event_t *event) {
for (inpos = 0, outpos = strlen("workspace \""); for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen; inpos < namelen;
inpos++, outpos++) { inpos++, outpos++) {
if (utf8_name[inpos] == '"') { if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
buffer[outpos] = '\\'; buffer[outpos] = '\\';
outpos++; outpos++;
} }
@ -498,7 +543,7 @@ void handle_button(xcb_button_press_event_t *event) {
/* /*
* Handle visibility notifications: when none of the bars are visible, e.g. * Handle visibility notifications: when none of the bars are visible, e.g.
* if windows are in full-screen on each output, suspend the child process. * if windows are in fullscreen on each output, suspend the child process.
* *
*/ */
static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
@ -506,7 +551,7 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
int num_visible = 0; int num_visible = 0;
i3_output *output; i3_output *output;
SLIST_FOREACH (output, outputs, slist) { SLIST_FOREACH(output, outputs, slist) {
if (!output->active) { if (!output->active) {
continue; continue;
} }
@ -863,7 +908,7 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
DLOG("xembed version = %d\n", xembed[0]); DLOG("xembed version = %d\n", xembed[0]);
DLOG("xembed flags = %d\n", xembed[1]); DLOG("xembed flags = %d\n", xembed[1]);
bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
DLOG("map-state now %d\n", map_it); DLOG("map state now %d\n", map_it);
if (trayclient->mapped && !map_it) { if (trayclient->mapped && !map_it) {
/* need to unmap the window */ /* need to unmap the window */
xcb_unmap_window(xcb_connection, trayclient->win); xcb_unmap_window(xcb_connection, trayclient->win);
@ -1002,7 +1047,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
redraw_bars(); redraw_bars();
break; break;
case XCB_BUTTON_PRESS: case XCB_BUTTON_PRESS:
/* Button-press-events are mouse-buttons clicked on one of our bars */ /* Button press events are mouse buttons clicked on one of our bars */
handle_button((xcb_button_press_event_t *)event); handle_button((xcb_button_press_event_t *)event);
break; break;
case XCB_CLIENT_MESSAGE: case XCB_CLIENT_MESSAGE:
@ -1035,7 +1080,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
} }
/* /*
* Dummy Callback. We only need this, so that the Prepare- and Check-Watchers * Dummy callback. We only need this, so that the prepare and check watchers
* are triggered * are triggered
* *
*/ */
@ -1048,7 +1093,7 @@ void xcb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* *
*/ */
char *init_xcb_early() { char *init_xcb_early() {
/* FIXME: xcb_connect leaks Memory */ /* FIXME: xcb_connect leaks memory */
xcb_connection = xcb_connect(NULL, &screen); xcb_connection = xcb_connect(NULL, &screen);
if (xcb_connection_has_error(xcb_connection)) { if (xcb_connection_has_error(xcb_connection)) {
ELOG("Cannot open display\n"); ELOG("Cannot open display\n");
@ -1091,7 +1136,7 @@ char *init_xcb_early() {
root_screen->width_in_pixels, root_screen->width_in_pixels,
root_screen->height_in_pixels); root_screen->height_in_pixels);
/* The various Watchers to communicate with xcb */ /* The various watchers to communicate with xcb */
xcb_io = smalloc(sizeof(ev_io)); xcb_io = smalloc(sizeof(ev_io));
xcb_prep = smalloc(sizeof(ev_prepare)); xcb_prep = smalloc(sizeof(ev_prepare));
xcb_chk = smalloc(sizeof(ev_check)); xcb_chk = smalloc(sizeof(ev_check));
@ -1109,9 +1154,9 @@ char *init_xcb_early() {
char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen); char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen);
if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") || if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") ||
xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") || xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") ||
xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) { xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -1119,7 +1164,7 @@ char *init_xcb_early() {
} }
/* /*
* Register for xkb keyevents. To grab modifiers without blocking other applications from receiving key-events * Register for xkb keyevents. To grab modifiers without blocking other applications from receiving key events
* involving that modifier, we sadly have to use xkb which is not yet fully supported * involving that modifier, we sadly have to use xkb which is not yet fully supported
* in xcb. * in xcb.
* *
@ -1171,8 +1216,11 @@ void init_xcb_late(char *fontname) {
/* Load the font */ /* Load the font */
font = load_font(fontname, true); font = load_font(fontname, true);
set_font(&font); set_font(&font);
DLOG("Calculated Font-height: %d\n", font.height); DLOG("Calculated font height: %d\n", font.height);
bar_height = font.height + logical_px(6); bar_height = font.height + 2 * logical_px(ws_voff_px);
if (config.separator_symbol)
separator_symbol_width = predict_text_width(config.separator_symbol);
xcb_flush(xcb_connection); xcb_flush(xcb_connection);
@ -1285,7 +1333,7 @@ void init_tray(void) {
/* /*
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window * We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset, * to make GTK+ 3 applets with symbolic icons visible. If the colors are unset,
* they assume a light background. * they assume a light background.
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591 * See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
* *
@ -1325,7 +1373,7 @@ void init_tray_colors(void) {
} }
/* /*
* Cleanup the xcb-stuff. * Cleanup the xcb stuff.
* Called once, before the program terminates. * Called once, before the program terminates.
* *
*/ */
@ -1370,7 +1418,7 @@ void get_atoms(void) {
free(reply); free(reply);
#include "xcb_atoms.def" #include "xcb_atoms.def"
DLOG("Got Atoms\n"); DLOG("Got atoms\n");
} }
/* /*
@ -1435,11 +1483,11 @@ void destroy_window(i3_output *output) {
} }
/* /*
* Reallocate the statusline-buffer * Reallocate the statusline buffer
* *
*/ */
void realloc_sl_buffer(void) { void realloc_sl_buffer(void) {
DLOG("Re-allocating statusline-buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n", DLOG("Re-allocating statusline buffer, statusline_width = %d, root_screen->width_in_pixels = %d\n",
statusline_width, root_screen->width_in_pixels); statusline_width, root_screen->width_in_pixels);
xcb_free_pixmap(xcb_connection, statusline_pm); xcb_free_pixmap(xcb_connection, statusline_pm);
statusline_pm = xcb_generate_id(xcb_connection); statusline_pm = xcb_generate_id(xcb_connection);
@ -1470,13 +1518,57 @@ void realloc_sl_buffer(void) {
mask, mask,
vals); vals);
if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") || if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline buffer") ||
xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") || xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline buffer clearcontext") ||
xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) { xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline buffer context")) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
/* Strut partial tells i3 where to reserve space for i3bar. This is determined
* by the `position` bar config directive. */
xcb_void_cookie_t config_strut_partial(i3_output *output) {
/* A local struct to save the strut_partial property */
struct {
uint32_t left;
uint32_t right;
uint32_t top;
uint32_t bottom;
uint32_t left_start_y;
uint32_t left_end_y;
uint32_t right_start_y;
uint32_t right_end_y;
uint32_t top_start_x;
uint32_t top_end_x;
uint32_t bottom_start_x;
uint32_t bottom_end_x;
} __attribute__((__packed__)) strut_partial;
memset(&strut_partial, 0, sizeof(strut_partial));
switch (config.position) {
case POS_NONE:
break;
case POS_TOP:
strut_partial.top = bar_height;
strut_partial.top_start_x = output->rect.x;
strut_partial.top_end_x = output->rect.x + output->rect.w;
break;
case POS_BOT:
strut_partial.bottom = bar_height;
strut_partial.bottom_start_x = output->rect.x;
strut_partial.bottom_end_x = output->rect.x + output->rect.w;
break;
}
return xcb_change_property(xcb_connection,
XCB_PROP_MODE_REPLACE,
output->bar,
atoms[_NET_WM_STRUT_PARTIAL],
XCB_ATOM_CARDINAL,
32,
12,
&strut_partial);
}
/* /*
* Reconfigure all bars and create new bars for recently activated outputs * Reconfigure all bars and create new bars for recently activated outputs
* *
@ -1496,14 +1588,14 @@ void reconfig_windows(bool redraw_bars) {
continue; continue;
} }
if (walk->bar == XCB_NONE) { if (walk->bar == XCB_NONE) {
DLOG("Creating Window for output %s\n", walk->name); DLOG("Creating window for output %s\n", walk->name);
walk->bar = xcb_generate_id(xcb_connection); walk->bar = xcb_generate_id(xcb_connection);
walk->buffer = xcb_generate_id(xcb_connection); walk->buffer = xcb_generate_id(xcb_connection);
mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
/* Black background */ /* Black background */
values[0] = colors.bar_bg; values[0] = colors.bar_bg;
/* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar-windows */ /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar windows */
values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1); values[1] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
/* We enable the following EventMask fields: /* We enable the following EventMask fields:
* EXPOSURE, to get expose events (we have to re-draw then) * EXPOSURE, to get expose events (we have to re-draw then)
@ -1565,7 +1657,7 @@ void reconfig_windows(bool redraw_bars) {
name); name);
free(name); free(name);
/* We want dock-windows (for now). When override_redirect is set, i3 is ignoring /* We want dock windows (for now). When override_redirect is set, i3 is ignoring
* this one */ * this one */
xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection, xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection,
XCB_PROP_MODE_REPLACE, XCB_PROP_MODE_REPLACE,
@ -1576,51 +1668,9 @@ void reconfig_windows(bool redraw_bars) {
1, 1,
(unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]); (unsigned char *)&atoms[_NET_WM_WINDOW_TYPE_DOCK]);
/* We need to tell i3, where to reserve space for i3bar */ xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
/* left, right, top, bottom, left_start_y, left_end_y,
* right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x,
* bottom_end_x */
/* A local struct to save the strut_partial property */
struct {
uint32_t left;
uint32_t right;
uint32_t top;
uint32_t bottom;
uint32_t left_start_y;
uint32_t left_end_y;
uint32_t right_start_y;
uint32_t right_end_y;
uint32_t top_start_x;
uint32_t top_end_x;
uint32_t bottom_start_x;
uint32_t bottom_end_x;
} __attribute__((__packed__)) strut_partial;
memset(&strut_partial, 0, sizeof(strut_partial));
switch (config.position) { /* We also want a graphics context for the bars (it defines the properties
case POS_NONE:
break;
case POS_TOP:
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 = bar_height;
strut_partial.bottom_start_x = walk->rect.x;
strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
break;
}
xcb_void_cookie_t strut_cookie = xcb_change_property(xcb_connection,
XCB_PROP_MODE_REPLACE,
walk->bar,
atoms[_NET_WM_STRUT_PARTIAL],
XCB_ATOM_CARDINAL,
32,
12,
&strut_partial);
/* We also want a graphics-context for the bars (it defines the properties
* with which we draw to them) */ * with which we draw to them) */
walk->bargc = xcb_generate_id(xcb_connection); walk->bargc = xcb_generate_id(xcb_connection);
xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection, xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
@ -1670,15 +1720,21 @@ void reconfig_windows(bool redraw_bars) {
XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE; XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x; values[0] = walk->rect.x;
if (config.position == POS_TOP)
values[1] = walk->rect.y;
else
values[1] = walk->rect.y + walk->rect.h - bar_height; values[1] = walk->rect.y + walk->rect.h - bar_height;
values[2] = walk->rect.w; values[2] = walk->rect.w;
values[3] = bar_height; values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE; values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Reconfiguring strut partial property for output %s\n", walk->name);
xcb_void_cookie_t strut_cookie = config_strut_partial(walk);
DLOG("Destroying buffer for output %s\n", walk->name); DLOG("Destroying buffer for output %s\n", walk->name);
xcb_free_pixmap(xcb_connection, walk->buffer); xcb_free_pixmap(xcb_connection, walk->buffer);
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]); DLOG("Reconfiguring window for output %s to %d,%d\n", walk->name, values[0], values[1]);
xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection, xcb_void_cookie_t cfg_cookie = xcb_configure_window_checked(xcb_connection,
walk->bar, walk->bar,
mask, mask,
@ -1686,7 +1742,7 @@ void reconfig_windows(bool redraw_bars) {
mask = XCB_CW_OVERRIDE_REDIRECT; mask = XCB_CW_OVERRIDE_REDIRECT;
values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1); values[0] = (config.hide_on_modifier == M_DOCK ? 0 : 1);
DLOG("Changing Window attribute override_redirect for output %s to %d\n", walk->name, values[0]); DLOG("Changing window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection, xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection,
walk->bar, walk->bar,
mask, mask,
@ -1723,6 +1779,7 @@ void reconfig_windows(bool redraw_bars) {
if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") || if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
xcb_request_failed(chg_cookie, "Could not change window") || xcb_request_failed(chg_cookie, "Could not change window") ||
xcb_request_failed(pm_cookie, "Could not create pixmap") || xcb_request_failed(pm_cookie, "Could not create pixmap") ||
xcb_request_failed(strut_cookie, "Could not set strut") ||
(redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") || (redraw_bars && (xcb_request_failed(umap_cookie, "Could not unmap window") ||
(config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) { (config.hide_on_modifier == M_DOCK && xcb_request_failed(map_cookie, "Could not map window"))))) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -1736,10 +1793,10 @@ void reconfig_windows(bool redraw_bars) {
* *
*/ */
void draw_bars(bool unhide) { void draw_bars(bool unhide) {
DLOG("Drawing Bars...\n"); DLOG("Drawing bars...\n");
int i = 0; int workspace_width = 0;
refresh_statusline(); refresh_statusline(false);
i3_output *outputs_walk; i3_output *outputs_walk;
SLIST_FOREACH(outputs_walk, outputs, slist) { SLIST_FOREACH(outputs_walk, outputs, slist) {
@ -1764,39 +1821,11 @@ void draw_bars(bool unhide) {
1, 1,
&rect); &rect);
if (!TAILQ_EMPTY(&statusline_head)) {
DLOG("Printing statusline!\n");
/* Luckily we already prepared a seperate pixmap containing the rendered
* statusline, we just have to copy the relevant parts to the relevant
* position */
trayclient *trayclient;
int traypx = 0;
TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) {
if (!trayclient->mapped)
continue;
/* We assume the tray icons are quadratic (we use the font
* *height* as *width* of the icons) because we configured them
* like this. */
traypx += font.height + logical_px(2);
}
/* Add 2px of padding if there are any tray icons */
if (traypx > 0)
traypx += logical_px(2);
xcb_copy_area(xcb_connection,
statusline_pm,
outputs_walk->buffer,
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + logical_px(4))), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - logical_px(4))), 0,
MIN(outputs_walk->rect.w - traypx - logical_px(4), (int)statusline_width), bar_height);
}
if (!config.disable_ws) { if (!config.disable_ws) {
i3_ws *ws_walk; i3_ws *ws_walk;
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
DLOG("Drawing Button for WS %s at x = %d, len = %d\n", DLOG("Drawing button for WS %s at x = %d, len = %d\n",
i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); i3string_as_utf8(ws_walk->name), workspace_width, ws_walk->name_width);
uint32_t fg_color = colors.inactive_ws_fg; uint32_t fg_color = colors.inactive_ws_fg;
uint32_t bg_color = colors.inactive_ws_bg; uint32_t bg_color = colors.inactive_ws_bg;
uint32_t border_color = colors.inactive_ws_border; uint32_t border_color = colors.inactive_ws_border;
@ -1824,10 +1853,10 @@ void draw_bars(bool unhide) {
outputs_walk->bargc, outputs_walk->bargc,
mask, mask,
vals_border); vals_border);
xcb_rectangle_t rect_border = {i, xcb_rectangle_t rect_border = {workspace_width,
logical_px(1), logical_px(1),
ws_walk->name_width + logical_px(10), ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
font.height + logical_px(4)}; font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)};
xcb_poly_fill_rectangle(xcb_connection, xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer, outputs_walk->buffer,
outputs_walk->bargc, outputs_walk->bargc,
@ -1838,10 +1867,10 @@ void draw_bars(bool unhide) {
outputs_walk->bargc, outputs_walk->bargc,
mask, mask,
vals); vals);
xcb_rectangle_t rect = {i + logical_px(1), xcb_rectangle_t rect = {workspace_width + logical_px(1),
2 * logical_px(1), 2 * logical_px(1),
ws_walk->name_width + logical_px(8), ws_walk->name_width + 2 * logical_px(ws_hoff_px),
font.height + logical_px(2)}; font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)};
xcb_poly_fill_rectangle(xcb_connection, xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer, outputs_walk->buffer,
outputs_walk->bargc, outputs_walk->bargc,
@ -1849,12 +1878,19 @@ void draw_bars(bool unhide) {
&rect); &rect);
set_font_colors(outputs_walk->bargc, fg_color, bg_color); set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc,
i + logical_px(5), 3 * logical_px(1), ws_walk->name_width); workspace_width + logical_px(ws_hoff_px) + logical_px(1),
i += logical_px(10) + ws_walk->name_width + logical_px(1); logical_px(ws_voff_px),
ws_walk->name_width);
workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width;
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
workspace_width += logical_px(ws_spacing_px);
} }
} }
if (binding.name && !config.disable_binding_mode_indicator) { if (binding.name && !config.disable_binding_mode_indicator) {
workspace_width += logical_px(ws_spacing_px);
uint32_t fg_color = colors.urgent_ws_fg; uint32_t fg_color = colors.urgent_ws_fg;
uint32_t bg_color = colors.urgent_ws_bg; uint32_t bg_color = colors.urgent_ws_bg;
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
@ -1864,7 +1900,10 @@ void draw_bars(bool unhide) {
outputs_walk->bargc, outputs_walk->bargc,
mask, mask,
vals_border); vals_border);
xcb_rectangle_t rect_border = {i, 1, binding.width + 10, font.height + 4}; xcb_rectangle_t rect_border = {workspace_width,
logical_px(1),
binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)};
xcb_poly_fill_rectangle(xcb_connection, xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer, outputs_walk->buffer,
outputs_walk->bargc, outputs_walk->bargc,
@ -1876,7 +1915,10 @@ void draw_bars(bool unhide) {
outputs_walk->bargc, outputs_walk->bargc,
mask, mask,
vals); vals);
xcb_rectangle_t rect = {i + 1, 2, binding.width + 8, font.height + 2}; xcb_rectangle_t rect = {workspace_width + logical_px(1),
2 * logical_px(1),
binding.width + 2 * logical_px(ws_hoff_px),
font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)};
xcb_poly_fill_rectangle(xcb_connection, xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer, outputs_walk->buffer,
outputs_walk->bargc, outputs_walk->bargc,
@ -1884,12 +1926,41 @@ void draw_bars(bool unhide) {
&rect); &rect);
set_font_colors(outputs_walk->bargc, fg_color, bg_color); set_font_colors(outputs_walk->bargc, fg_color, bg_color);
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width); draw_text(binding.name,
outputs_walk->buffer,
outputs_walk->bargc,
workspace_width + logical_px(ws_hoff_px) + logical_px(1),
logical_px(ws_voff_px),
binding.width);
unhide = true; unhide = true;
workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width;
} }
i = 0; if (!TAILQ_EMPTY(&statusline_head)) {
DLOG("Printing statusline!\n");
int tray_width = get_tray_width(outputs_walk->trayclients);
uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - 2 * logical_px(sb_hoff_px);
/* If the statusline is too long, try to use short texts. */
if (statusline_width > max_statusline_width)
refresh_statusline(true);
/* Luckily we already prepared a seperate pixmap containing the rendered
* statusline, we just have to copy the relevant parts to the relevant
* position */
int visible_statusline_width = MIN(statusline_width, max_statusline_width);
xcb_copy_area(xcb_connection,
statusline_pm,
outputs_walk->buffer,
outputs_walk->bargc,
(int16_t)(statusline_width - visible_statusline_width), 0,
(int16_t)(outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width), 0,
(int16_t)visible_statusline_width, (int16_t)bar_height);
}
workspace_width = 0;
} }
/* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */ /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */

View File

@ -105,7 +105,7 @@ struct Config {
/** By default, focus follows mouse. If the user explicitly wants to /** By default, focus follows mouse. If the user explicitly wants to
* turn this off (and instead rely only on the keyboard for changing * turn this off (and instead rely only on the keyboard for changing
* focus), we allow him to do this with this relatively special option. * focus), we allow them to do this with this relatively special option.
* It is not planned to add any different focus models. */ * It is not planned to add any different focus models. */
bool disable_focus_follows_mouse; bool disable_focus_follows_mouse;
@ -285,6 +285,9 @@ struct Barconfig {
/** Font specification for all text rendered on the bar. */ /** Font specification for all text rendered on the bar. */
char *font; char *font;
/** A custom separator to use instead of a vertical line. */
char *separator_symbol;
/** Hide workspace buttons? Configuration option is 'workspace_buttons no' /** Hide workspace buttons? Configuration option is 'workspace_buttons no'
* but we invert the bool to get the correct default when initializing with * but we invert the bool to get the correct default when initializing with
* zero. */ * zero. */

View File

@ -67,6 +67,7 @@ CFGFUN(enter_mode, const char *mode);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command); CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
CFGFUN(bar_font, const char *font); CFGFUN(bar_font, const char *font);
CFGFUN(bar_separator_symbol, const char *separator);
CFGFUN(bar_mode, const char *mode); CFGFUN(bar_mode, const char *mode);
CFGFUN(bar_hidden_state, const char *hidden_state); CFGFUN(bar_hidden_state, const char *hidden_state);
CFGFUN(bar_id, const char *bar_id); CFGFUN(bar_id, const char *bar_id);

View File

@ -450,7 +450,7 @@ struct Match {
/** /**
* An Assignment makes specific windows go to a specific workspace/output or * An Assignment makes specific windows go to a specific workspace/output or
* run a command for that window. With this mechanism, the user can -- for * run a command for that window. With this mechanism, the user can -- for
* example -- assign his browser to workspace "www". Checking if a window is * example -- assign their browser to workspace "www". Checking if a window is
* assigned works by comparing the Match data structure with the window (see * assigned works by comparing the Match data structure with the window (see
* match_matches_window()). * match_matches_window()).
* *
@ -460,7 +460,6 @@ struct Assignment {
* *
* A_COMMAND = run the specified command for the matching window * A_COMMAND = run the specified command for the matching window
* A_TO_WORKSPACE = assign the matching window to the specified workspace * A_TO_WORKSPACE = assign the matching window to the specified workspace
* A_TO_OUTPUT = assign the matching window to the specified output
* *
* While the type is a bitmask, only one value can be set at a time. It is * While the type is a bitmask, only one value can be set at a time. It is
* a bitmask to allow filtering for multiple types, for example in the * a bitmask to allow filtering for multiple types, for example in the
@ -470,18 +469,16 @@ struct Assignment {
enum { enum {
A_ANY = 0, A_ANY = 0,
A_COMMAND = (1 << 0), A_COMMAND = (1 << 0),
A_TO_WORKSPACE = (1 << 1), A_TO_WORKSPACE = (1 << 1)
A_TO_OUTPUT = (1 << 2)
} type; } type;
/** the criteria to check if a window matches */ /** the criteria to check if a window matches */
Match match; Match match;
/** destination workspace/output/command, depending on the type */ /** destination workspace/command, depending on the type */
union { union {
char *command; char *command;
char *workspace; char *workspace;
char *output;
} dest; } dest;
TAILQ_ENTRY(Assignment) assignments; TAILQ_ENTRY(Assignment) assignments;

View File

@ -19,7 +19,7 @@ void handle_key_press(xcb_key_press_event_t *event);
/** /**
* Kills the commanderror i3-nagbar process, if any. * Kills the commanderror i3-nagbar process, if any.
* *
* Called when reloading/restarting, since the user probably fixed his wrong * Called when reloading/restarting, since the user probably fixed their wrong
* keybindings. * keybindings.
* *
* If wait_for_it is set (restarting), this function will waitpid(), otherwise, * If wait_for_it is set (restarting), this function will waitpid(), otherwise,

View File

@ -134,6 +134,20 @@ char *sstrdup(const char *str);
*/ */
int sasprintf(char **strp, const char *fmt, ...); int sasprintf(char **strp, const char *fmt, ...);
/**
* Wrapper around correct write which returns -1 (meaning that
* write failed) or count (meaning that all bytes were written)
*
*/
ssize_t writeall(int fd, const void *buf, size_t count);
/**
* Safe-wrapper around writeall which exits if it returns -1 (meaning that
* write failed)
*
*/
ssize_t swrite(int fd, const void *buf, size_t count);
/** /**
* Build an i3String from an UTF-8 encoded string. * Build an i3String from an UTF-8 encoded string.
* Returns the newly-allocated i3String. * Returns the newly-allocated i3String.
@ -169,6 +183,12 @@ i3String *i3string_from_markup_with_length(const char *from_markup, size_t num_b
*/ */
i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs); i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs);
/**
* Copies the given i3string.
* Note that this will not free the source string.
*/
i3String *i3string_copy(i3String *str);
/** /**
* Free an i3String. * Free an i3String.
* *
@ -211,6 +231,11 @@ size_t i3string_get_num_bytes(i3String *str);
*/ */
bool i3string_is_markup(i3String *str); bool i3string_is_markup(i3String *str);
/**
* Set whether the i3String should use Pango markup.
*/
void i3string_set_markup(i3String *str, bool is_markup);
/** /**
* Returns the number of glyphs in an i3String. * Returns the number of glyphs in an i3String.
* *

View File

@ -14,3 +14,10 @@
* *
*/ */
Con *output_get_content(Con *output); Con *output_get_content(Con *output);
/**
* Returns an 'output' corresponding to one of left/right/down/up or a specific
* output name.
*
*/
Output *get_output_from_string(Output *current_output, const char *output_str);

View File

@ -44,6 +44,12 @@ void startup_sequence_delete(struct Startup_Sequence *sequence);
*/ */
void startup_monitor_event(SnMonitorEvent *event, void *userdata); void startup_monitor_event(SnMonitorEvent *event, void *userdata);
/**
* Renames workspaces that are mentioned in the startup sequences.
*
*/
void startup_sequence_rename_workspace(char *old_name, char *new_name);
/** /**
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
* *

View File

@ -179,3 +179,10 @@ Con *workspace_attach_to(Con *ws);
* The container inherits the layout from the workspace. * The container inherits the layout from the workspace.
*/ */
Con *workspace_encapsulate(Con *ws); Con *workspace_encapsulate(Con *ws);
/**
* Move the given workspace to the specified output.
* This returns true if and only if moving the workspace was successful.
*
*/
bool workspace_move_to_output(Con *ws, char *output);

View File

@ -17,6 +17,12 @@ extern xcb_screen_t *root_screen;
* *
*/ */
int logical_px(const int logical) { int logical_px(const int logical) {
if (root_screen == NULL) {
/* Dpi info may not be available when parsing a config without an X
* server, such as for config file validation. */
return logical;
}
const int dpi = (double)root_screen->height_in_pixels * 25.4 / const int dpi = (double)root_screen->height_in_pixels * 25.4 /
(double)root_screen->height_in_millimeters; (double)root_screen->height_in_millimeters;
/* There are many misconfigurations out there, i.e. systems with screens /* There are many misconfigurations out there, i.e. systems with screens

View File

@ -126,7 +126,10 @@ static void draw_text_pango(const char *text, size_t text_len,
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
pango_cairo_update_layout(cr, layout); pango_cairo_update_layout(cr, layout);
pango_layout_get_pixel_size(layout, NULL, &height); pango_layout_get_pixel_size(layout, NULL, &height);
cairo_move_to(cr, x, y - 0.5 * (height - savedFont->height)); /* Center the piece of text vertically if its height is smaller than the
* cached font height, and just let "high" symbols fall out otherwise. */
int yoffset = (height < savedFont->height ? 0.5 : 1) * (height - savedFont->height);
cairo_move_to(cr, x, y - yoffset);
pango_cairo_show_layout(cr, layout); pango_cairo_show_layout(cr, layout);
/* Free resources */ /* Free resources */
@ -455,7 +458,7 @@ static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) {
cookie, &error); cookie, &error);
if (reply == NULL) { if (reply == NULL) {
/* We return a safe estimate because a rendering error is better than /* We return a safe estimate because a rendering error is better than
* a crash. Plus, the user will see the error in his log. */ * a crash. Plus, the user will see the error in their log. */
fprintf(stderr, "Could not get text extents (X error code %d)\n", fprintf(stderr, "Could not get text extents (X error code %d)\n",
error->error_code); error->error_code);
return savedFont->specific.xcb.info->max_bounds.character_width * text_len; return savedFont->specific.xcb.info->max_bounds.character_width * text_len;

View File

@ -32,33 +32,11 @@ int ipc_send_message(int sockfd, const uint32_t message_size,
.size = message_size, .size = message_size,
.type = message_type}; .type = message_type};
size_t sent_bytes = 0; if (writeall(sockfd, ((void *)&header), sizeof(i3_ipc_header_t)) == -1)
int n = 0;
/* This first loop is basically unnecessary. No operating system has
* buffers which cannot fit 14 bytes into them, so the write() will only be
* called once. */
while (sent_bytes < sizeof(i3_ipc_header_t)) {
if ((n = write(sockfd, ((void *)&header) + sent_bytes, sizeof(i3_ipc_header_t) - sent_bytes)) == -1) {
if (errno == EAGAIN)
continue;
return -1; return -1;
}
sent_bytes += n; if (writeall(sockfd, payload, message_size) == -1)
}
sent_bytes = 0;
while (sent_bytes < message_size) {
if ((n = write(sockfd, payload + sent_bytes, message_size - sent_bytes)) == -1) {
if (errno == EAGAIN)
continue;
return -1; return -1;
}
sent_bytes += n;
}
return 0; return 0;
} }

View File

@ -8,8 +8,10 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
#include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <err.h> #include <err.h>
#include <errno.h>
#include "libi3.h" #include "libi3.h"
@ -56,3 +58,30 @@ int sasprintf(char **strp, const char *fmt, ...) {
va_end(args); va_end(args);
return result; return result;
} }
ssize_t writeall(int fd, const void *buf, size_t count) {
size_t written = 0;
ssize_t n = 0;
while (written < count) {
n = write(fd, buf + written, count - written);
if (n == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
return n;
}
written += (size_t)n;
}
return written;
}
ssize_t swrite(int fd, const void *buf, size_t count) {
ssize_t n;
n = writeall(fd, buf, count);
if (n == -1)
err(EXIT_FAILURE, "Failed to write %d", fd);
else
return n;
}

View File

@ -109,6 +109,16 @@ i3String *i3string_from_ucs2(const xcb_char2b_t *from_ucs2, size_t num_glyphs) {
return str; return str;
} }
/**
* Copies the given i3string.
* Note that this will not free the source string.
*/
i3String *i3string_copy(i3String *str) {
i3String *copy = i3string_from_utf8(i3string_as_utf8(str));
copy->is_markup = str->is_markup;
return copy;
}
/* /*
* Free an i3String. * Free an i3String.
* *
@ -168,6 +178,13 @@ bool i3string_is_markup(i3String *str) {
return str->is_markup; return str->is_markup;
} }
/*
* Set whether the i3String should use Pango markup.
*/
void i3string_set_markup(i3String *str, bool is_markup) {
str->is_markup = is_markup;
}
/* /*
* Returns the number of glyphs in an i3String. * Returns the number of glyphs in an i3String.
* *

View File

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

View File

@ -30,7 +30,7 @@ It tries to start one of the following (in that order):
* gedit * gedit
* mc-edit * mc-edit
Please dont complain about the order: If the user has any preference, he will Please dont complain about the order: If the user has any preference, they will
have $VISUAL or $EDITOR set. have $VISUAL or $EDITOR set.
== SEE ALSO == SEE ALSO

View File

@ -23,7 +23,7 @@ It tries to start one of the following (in that order):
* w3m * w3m
* i3-sensible-editor(1) * i3-sensible-editor(1)
Please dont complain about the order: If the user has any preference, he will Please dont complain about the order: If the user has any preference, they will
have $PAGER set. have $PAGER set.
== SEE ALSO == SEE ALSO

View File

@ -33,8 +33,8 @@ It tries to start one of the following (in that order):
* roxterm * roxterm
* xfce4-terminal * xfce4-terminal
Please dont complain about the order: If the user has any preference, she will Please dont complain about the order: If the user has any preference, they will
have $TERMINAL set or modified her i3 configuration file. have $TERMINAL set or modified their i3 configuration file.
== SEE ALSO == SEE ALSO

View File

@ -353,6 +353,8 @@ state MODE:
state NOP: state NOP:
comment = string comment = string
-> call cmd_nop($comment) -> call cmd_nop($comment)
end
-> call cmd_nop(NULL)
state SCRATCHPAD: state SCRATCHPAD:
'show' 'show'

View File

@ -370,6 +370,7 @@ state BAR:
'output' -> BAR_OUTPUT 'output' -> BAR_OUTPUT
'tray_output' -> BAR_TRAY_OUTPUT 'tray_output' -> BAR_TRAY_OUTPUT
'font' -> BAR_FONT 'font' -> BAR_FONT
'separator_symbol' -> BAR_SEPARATOR_SYMBOL
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR 'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS 'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
@ -435,6 +436,10 @@ state BAR_FONT:
font = string font = string
-> call cfg_bar_font($font); BAR -> call cfg_bar_font($font); BAR
state BAR_SEPARATOR_SYMBOL:
separator = string
-> call cfg_bar_separator_symbol($separator); BAR
state BAR_BINDING_MODE_INDICATOR: state BAR_BINDING_MODE_INDICATOR:
value = word value = word
-> call cfg_bar_binding_mode_indicator($value); BAR -> call cfg_bar_binding_mode_indicator($value); BAR

193
release.sh Executable file
View File

@ -0,0 +1,193 @@
#!/bin/zsh
# This script is used to prepare a new release of i3.
export RELEASE_VERSION="4.10.1"
export PREVIOUS_VERSION="4.10"
export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
then
echo "../i3.github.io does not exist."
echo "Use git clone git://github.com/i3/i3.github.io"
exit 1
fi
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
then
echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
exit 1
fi
if git diff-files --quiet --exit-code debian/changelog
then
echo "Expected debian/changelog to be changed (containing the changelog for ${RELEASE_VERSION})."
exit 1
fi
eval $(gpg-agent --daemon)
export GPG_AGENT_INFO
################################################################################
# Section 1: update git and build the release tarball
################################################################################
STARTDIR=$PWD
TMPDIR=$(mktemp -d)
cd $TMPDIR
if ! wget http://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then
echo "Could not download i3-${PREVIOUS_VERSION}.tar.bz2 (required for comparing files)."
exit 1
fi
git clone --quiet --branch "${RELEASE_BRANCH}" file://${STARTDIR}
cd i3
if [ ! -e "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" ]; then
echo "Required file RELEASE-NOTES-${RELEASE_VERSION} not found."
exit 1
fi
git checkout -b release-${RELEASE_VERSION}
cp "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" "RELEASE-NOTES-${RELEASE_VERSION}"
git add RELEASE-NOTES-${RELEASE_VERSION}
git rm RELEASE-NOTES-${PREVIOUS_VERSION}
sed -i "s,<refmiscinfo class=\"version\">[^<]*</refmiscinfo>,<refmiscinfo class=\"version\">${RELEASE_VERSION}</refmiscinfo>,g" man/asciidoc.conf
git commit -a -m "release i3 ${RELEASE_VERSION}"
git tag "${RELEASE_VERSION}" -m "release i3 ${RELEASE_VERSION}" --sign --local-user=0x4AC8EE1D
make dist
echo "Differences in the release tarball file lists:"
diff -u \
<(tar tf ../i3-${PREVIOUS_VERSION}.tar.bz2 | sed "s,i3-${PREVIOUS_VERSION}/,,g" | sort) \
<(tar tf i3-${RELEASE_VERSION}.tar.bz2 | sed "s,i3-${RELEASE_VERSION}/,,g" | sort) \
| colordiff
if ! tar xf i3-${RELEASE_VERSION}.tar.bz2 --to-stdout --strip-components=1 i3-${RELEASE_VERSION}/I3_VERSION | grep -q "^${RELEASE_VERSION} "
then
echo "I3_VERSION file does not start with ${RELEASE_VERSION}"
exit 1
fi
gpg --armor -b i3-${RELEASE_VERSION}.tar.bz2
if [ "${RELEASE_BRANCH}" = "master" ]; then
git checkout master
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout next
git merge --no-ff master -m "Merge branch 'master' into next"
else
git checkout next
git merge --no-ff release-${RELEASE_VERSION} -m "Merge branch 'release-${RELEASE_VERSION}'"
git checkout master
git merge --no-ff next -m "Merge branch 'next' into master"
fi
################################################################################
# Section 2: Debian packaging
################################################################################
cd "${TMPDIR}"
mkdir debian
# Copy over the changelog because we expect it to be locally modified in the
# start directory.
cp "${STARTDIR}/debian/changelog" i3/debian/changelog
cat > ${TMPDIR}/Dockerfile <<EOT
FROM debian:sid
RUN sed -i 's,^deb \(.*\),deb \1\ndeb-src \1,g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y dpkg-dev devscripts
COPY i3/i3-${RELEASE_VERSION}.tar.bz2 /usr/src/i3-wm_${RELEASE_VERSION}.orig.tar.bz2
WORKDIR /usr/src/
RUN tar xf i3-wm_${RELEASE_VERSION}.orig.tar.bz2
WORKDIR /usr/src/i3-${RELEASE_VERSION}
COPY i3/debian /usr/src/i3-${RELEASE_VERSION}/debian/
RUN mkdir debian/source
RUN echo '3.0 (quilt)' > debian/source/format
WORKDIR /usr/src
RUN mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' i3-${RELEASE_VERSION}/debian/control
WORKDIR /usr/src/i3-${RELEASE_VERSION}
RUN dpkg-buildpackage -sa -j8
EOT
CONTAINER_NAME=$(echo "i3-${TMPDIR}" | sed 's,/,,g')
docker build -t i3 .
for file in $(docker run --name "${CONTAINER_NAME}" i3 /bin/sh -c "ls /usr/src/i3*_${RELEASE_VERSION}*")
do
docker cp "${CONTAINER_NAME}:${file}" ${TMPDIR}/debian/
done
echo "Content of resulting packages .changes file:"
cat ${TMPDIR}/debian/*.changes
# debsign is in devscripts, which is available in fedora and debian
debsign -k4AC8EE1D ${TMPDIR}/debian/*.changes
# TODO: docker cleanup
################################################################################
# Section 3: website
################################################################################
cd ${TMPDIR}
git clone --quiet ${STARTDIR}/../i3.github.io
cd i3.github.io
cp ${TMPDIR}/i3/i3-${RELEASE_VERSION}.tar.bz2* downloads/
git add downloads/i3-${RELEASE_VERSION}.tar.bz2*
cp ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION} downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
git add downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
sed -i "s,<h2>Documentation for i3 v[^<]*</h2>,<h2>Documentation for i3 v${RELEASE_VERSION}</h2>,g" docs/index.html
sed -i "s,<span style=\"margin-left: 2em; color: #c0c0c0\">[^<]*</span>,<span style=\"margin-left: 2em; color: #c0c0c0\">${RELEASE_VERSION}</span>,g" index.html
sed -i "s,The current stable version is .*$,The current stable version is ${RELEASE_VERSION}.,g" downloads/index.html
sed -i "s,<tbody>,<tbody>\n <tr>\n <td>${RELEASE_VERSION}</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.bz2\">i3-${RELEASE_VERSION}.tar.bz2</a></td>\n <td>$(ls -lh ../i3/i3-${RELEASE_VERSION}.tar.bz2 | awk -F " " {'print $5'} | sed 's/K$/ KiB/g')</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.bz2.asc\">signature</a></td>\n <td>$(date +'%Y-%m-%d')</td>\n <td><a href=\"/downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt\">release notes</a></td>\n </tr>\n,g" downloads/index.html
git commit -a -m "add ${RELEASE_VERSION} release"
mkdir docs/${PREVIOUS_VERSION}
tar cf - '--exclude=[0-9]\.[0-9e]*' docs | tar xf - --strip-components=1 -C docs/${PREVIOUS_VERSION}
git add docs/${PREVIOUS_VERSION}
git commit -a -m "save docs for ${PREVIOUS_VERSION}"
for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
do
base="$(basename $i)"
[ -e "${STARTDIR}/docs/${base}" ] && cp "${STARTDIR}/docs/${base}" "_docs/${base}"
done
(cd _docs && make)
for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
do
base="$(basename $i)"
[ -e "${STARTDIR}/docs/${base}" ] && cp "_docs/${base}.html" docs/
done
git commit -a -m "update docs for ${RELEASE_VERSION}"
################################################################################
# Section 4: final push instructions
################################################################################
echo "As a final sanity check, install the debian package and see whether i3 works."
echo "When satisfied, run:"
echo " cd ${TMPDIR}/i3"
echo " git checkout next"
echo " vi debian/changelog"
# TODO: can we just set up the remote spec properly?
echo " git push git@github.com:i3/i3 next"
echo " git push git@github.com:i3/i3 master"
echo " git push git@github.com:i3/i3 --tags"
echo ""
echo " cd ${TMPDIR}/i3.github.io"
# TODO: can we just set up the remote spec properly?
echo " git push git@github.com:i3/i3.github.io master"
echo ""
echo " cd ${TMPDIR}/debian"
echo " dput *.changes"
echo ""
echo "Announce on:"
echo " twitter"
echo " google+"
echo " mailing list"
echo " #i3 topic"

View File

@ -46,6 +46,9 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
case BORDER_BOTTOM: case BORDER_BOTTOM:
search_direction = D_DOWN; search_direction = D_DOWN;
break; break;
default:
assert(false);
break;
} }
bool res = resize_find_tiling_participants(&first, &second, search_direction); bool res = resize_find_tiling_participants(&first, &second, search_direction);
@ -380,11 +383,6 @@ int handle_button_press(xcb_button_press_event_t *event) {
return 0; return 0;
} }
if (event->child != XCB_NONE) {
DLOG("event->child not XCB_NONE, so this is an event which originated from a click into the application, but the application did not handle it.\n");
return route_click(con, event, mod_pressed, CLICK_INSIDE);
}
/* Check if the click was on the decoration of a child */ /* Check if the click was on the decoration of a child */
Con *child; Con *child;
TAILQ_FOREACH(child, &(con->nodes_head), nodes) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
@ -394,5 +392,10 @@ int handle_button_press(xcb_button_press_event_t *event) {
return route_click(child, event, mod_pressed, CLICK_DECORATION); return route_click(child, event, mod_pressed, CLICK_DECORATION);
} }
if (event->child != XCB_NONE) {
DLOG("event->child not XCB_NONE, so this is an event which originated from a click into the application, but the application did not handle it.\n");
return route_click(con, event, mod_pressed, CLICK_INSIDE);
}
return route_click(con, event, mod_pressed, CLICK_BORDER); return route_click(con, event, mod_pressed, CLICK_BORDER);
} }

View File

@ -65,28 +65,6 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) {
return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
} }
/*
* Returns an 'output' corresponding to one of left/right/down/up or a specific
* output name.
*
*/
static Output *get_output_from_string(Output *current_output, const char *output_str) {
Output *output;
if (strcasecmp(output_str, "left") == 0)
output = get_output_next_wrap(D_LEFT, current_output);
else if (strcasecmp(output_str, "right") == 0)
output = get_output_next_wrap(D_RIGHT, current_output);
else if (strcasecmp(output_str, "up") == 0)
output = get_output_next_wrap(D_UP, current_output);
else if (strcasecmp(output_str, "down") == 0)
output = get_output_next_wrap(D_DOWN, current_output);
else
output = get_output_by_name(output_str);
return output;
}
/* /*
* Returns the output containing the given container. * Returns the output containing the given container.
*/ */
@ -516,27 +494,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
LOG("should move window to workspace %s\n", name); LOG("should move window to workspace %s\n", name);
/* get the workspace */ /* get the workspace */
Con *ws = NULL; Con *ws = workspace_get(name, NULL);
Con *output = NULL;
/* first look for a workspace with this name */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name));
}
/* if the name is plain digits, we interpret this as a "workspace number"
* command */
if (!ws && name_is_digits(name)) {
long parsed_num = ws_name_to_number(name);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output),
child->num == parsed_num);
}
}
/* if no workspace was found, make a new one */
if (!ws)
ws = workspace_get(name, NULL);
ws = maybe_auto_back_and_forth_workspace(ws); ws = maybe_auto_back_and_forth_workspace(ws);
@ -1071,30 +1029,7 @@ void cmd_workspace_name(I3_CMD, char *name) {
DLOG("should switch to workspace %s\n", name); DLOG("should switch to workspace %s\n", name);
if (maybe_back_and_forth(cmd_output, name)) if (maybe_back_and_forth(cmd_output, name))
return; return;
workspace_show_by_name(name);
Con *ws = NULL;
Con *output = NULL;
/* first look for a workspace with this name */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output), !strcasecmp(child->name, name));
}
/* if the name is only digits, we interpret this as a "workspace number"
* command */
if (!ws && name_is_digits(name)) {
long parsed_num = ws_name_to_number(name);
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
GREP_FIRST(ws, output_get_content(output),
child->num == parsed_num);
}
}
/* if no workspace was found, make a new one */
if (!ws)
ws = workspace_get(name, NULL);
workspace_show(ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@ -1177,28 +1112,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
/* get the output */
Output *current_output = NULL; Output *current_output = NULL;
Output *output;
// TODO: fix the handling of criteria // TODO: fix the handling of criteria
TAILQ_FOREACH(current, &owindows, owindows) TAILQ_FOREACH(current, &owindows, owindows)
current_output = get_output_of_con(current->con); current_output = get_output_of_con(current->con);
assert(current_output != NULL); assert(current_output != NULL);
// TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser Output *output = get_output_from_string(current_output, name);
if (strcasecmp(name, "up") == 0)
output = get_output_next_wrap(D_UP, current_output);
else if (strcasecmp(name, "down") == 0)
output = get_output_next_wrap(D_DOWN, current_output);
else if (strcasecmp(name, "left") == 0)
output = get_output_next_wrap(D_LEFT, current_output);
else if (strcasecmp(name, "right") == 0)
output = get_output_next_wrap(D_RIGHT, current_output);
else
output = get_output_by_name(name);
if (!output) { if (!output) {
LOG("No such output found.\n"); LOG("No such output found.\n");
ysuccess(false); ysuccess(false);
@ -1265,101 +1185,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
owindow *current; owindow *current;
TAILQ_FOREACH(current, &owindows, owindows) { TAILQ_FOREACH(current, &owindows, owindows) {
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);
return;
}
Output *output = get_output_from_string(current_output, name);
if (!output) {
ELOG("Could not get output from string \"%s\"\n", name);
ysuccess(false);
return;
}
Con *content = output_get_content(output->con);
LOG("got output %p with content %p\n", output, content);
Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
LOG("should move workspace %p / %s\n", ws, ws->name); bool success = workspace_move_to_output(ws, name);
bool workspace_was_visible = workspace_is_visible(ws); if (!success) {
ELOG("Failed to move workspace to output.\n");
if (con_num_children(ws->parent) == 1) { ysuccess(false);
LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); return;
/* check if we can find a workspace assigned to this output */
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->output, current_output->name) != 0)
continue;
/* check if this workspace is already attached to the tree */
Con *workspace = NULL, *out;
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(out),
!strcasecmp(child->name, assignment->name));
if (workspace != NULL)
continue;
/* so create the workspace referenced to by this assignment */
LOG("Creating workspace from assignment %s.\n", assignment->name);
workspace_get(assignment->name, NULL);
used_assignment = true;
break;
}
/* if we couldn't create the workspace using an assignment, create
* it on the output */
if (!used_assignment)
create_workspace_on_output(current_output, ws->parent);
/* notify the IPC listeners */
ipc_send_workspace_event("init", ws, NULL);
}
DLOG("Detaching\n");
/* detach from the old output and attach to the new output */
Con *old_content = ws->parent;
con_detach(ws);
if (workspace_was_visible) {
/* The workspace which we just detached was visible, so focus
* the next one in the focus-stack. */
Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
workspace_show(focus_ws);
}
con_attach(ws, content, false);
/* fix the coordinates of the floating containers */
Con *floating_con;
TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
ipc_send_workspace_event("move", ws, NULL);
if (workspace_was_visible) {
/* Focus the moved workspace on the destination output. */
workspace_show(ws);
}
/* NB: We cannot simply work with previously_visible_ws since it might
* have been cleaned up by workspace_show() already, depending on the
* focus order/number of other workspaces on the output.
* Instead, we loop through the available workspaces and only work with
* previously_visible_ws if we still find it. */
TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
if (ws != previously_visible_ws)
continue;
/* Call the on_remove_child callback of the workspace which previously
* was visible on the destination output. Since it is no longer
* visible, it might need to get cleaned up. */
CALL(previously_visible_ws, on_remove_child);
break;
} }
} }
@ -2034,6 +1865,24 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
Con *parent = workspace->parent; Con *parent = workspace->parent;
con_detach(workspace); con_detach(workspace);
con_attach(workspace, parent, false); con_attach(workspace, parent, false);
/* Move the workspace to the correct output if it has an assignment */
struct Workspace_Assignment *assignment = NULL;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (assignment->output == NULL)
continue;
if (strcmp(assignment->name, workspace->name) != 0 && (!name_is_digits(assignment->name) || ws_name_to_number(assignment->name) != workspace->num)) {
continue;
}
workspace_move_to_output(workspace, assignment->output);
if (previously_focused)
workspace_show(con_get_workspace(previously_focused));
break;
}
/* Restore the previous focus since con_attach messes with the focus. */ /* Restore the previous focus since con_attach messes with the focus. */
con_focus(previously_focused); con_focus(previously_focused);
@ -2044,6 +1893,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
ewmh_update_desktop_names(); ewmh_update_desktop_names();
ewmh_update_desktop_viewport(); ewmh_update_desktop_viewport();
ewmh_update_current_desktop(); ewmh_update_current_desktop();
startup_sequence_rename_workspace(old_name, new_name);
} }
/* /*

View File

@ -216,7 +216,8 @@ char *parse_string(const char **walk, bool as_word) {
if (**walk == '"') { if (**walk == '"') {
beginning++; beginning++;
(*walk)++; (*walk)++;
while (**walk != '\0' && (**walk != '"' || *(*walk - 1) == '\\')) for (; **walk != '\0' && **walk != '"'; (*walk)++)
if (**walk == '\\' && *(*walk + 1) != '\0')
(*walk)++; (*walk)++;
} else { } else {
if (!as_word) { if (!as_word) {
@ -248,10 +249,10 @@ char *parse_string(const char **walk, bool as_word) {
for (inpos = 0, outpos = 0; for (inpos = 0, outpos = 0;
inpos < (*walk - beginning); inpos < (*walk - beginning);
inpos++, outpos++) { inpos++, outpos++) {
/* We only handle escaped double quotes to not break /* We only handle escaped double quotes and backslashes to not break
* backwards compatibility with people using \w in * backwards compatibility with people using \w in regular expressions
* regular expressions etc. */ * etc. */
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') if (beginning[inpos] == '\\' && (beginning[inpos + 1] == '"' || beginning[inpos + 1] == '\\'))
inpos++; inpos++;
str[outpos] = beginning[inpos]; str[outpos] = beginning[inpos];
} }

View File

@ -1149,12 +1149,12 @@ Rect con_border_style_rect(Con *con) {
/* Shortcut to avoid calling con_adjacent_borders() on dock containers. */ /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
int border_style = con_border_style(con); int border_style = con_border_style(con);
if (border_style == BS_NONE) if (border_style == BS_NONE)
return (Rect) {0, 0, 0, 0}; return (Rect){0, 0, 0, 0};
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
if (border_style == BS_NORMAL) { if (border_style == BS_NORMAL) {
result = (Rect) {border_width, 0, -(2 * border_width), -(border_width)}; result = (Rect){border_width, 0, -(2 * border_width), -(border_width)};
} else { } else {
result = (Rect) {border_width, border_width, -(2 * border_width), -(2 * border_width)}; result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
} }
/* Floating windows are never adjacent to any other window, so /* Floating windows are never adjacent to any other window, so
@ -1473,7 +1473,7 @@ Rect con_minimum_size(Con *con) {
if (con_is_leaf(con)) { if (con_is_leaf(con)) {
DLOG("leaf node, returning 75x50\n"); DLOG("leaf node, returning 75x50\n");
return (Rect) {0, 0, 75, 50}; return (Rect){0, 0, 75, 50};
} }
if (con->type == CT_FLOATING_CON) { if (con->type == CT_FLOATING_CON) {
@ -1493,7 +1493,7 @@ Rect con_minimum_size(Con *con) {
} }
DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n", DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n",
max_width, max_height, deco_height); max_width, max_height, deco_height);
return (Rect) {0, 0, max_width, max_height + deco_height}; return (Rect){0, 0, max_width, max_height + deco_height};
} }
/* For horizontal/vertical split containers we sum up the width (h-split) /* For horizontal/vertical split containers we sum up the width (h-split)
@ -1513,7 +1513,7 @@ Rect con_minimum_size(Con *con) {
} }
} }
DLOG("split container, returning width = %d x height = %d\n", width, height); DLOG("split container, returning width = %d x height = %d\n", width, height);
return (Rect) {0, 0, width, height}; return (Rect){0, 0, width, height};
} }
ELOG("Unhandled case, type = %d, layout = %d, split = %d\n", ELOG("Unhandled case, type = %d, layout = %d, split = %d\n",

View File

@ -158,8 +158,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
assign = TAILQ_FIRST(&assignments); assign = TAILQ_FIRST(&assignments);
if (assign->type == A_TO_WORKSPACE) if (assign->type == A_TO_WORKSPACE)
FREE(assign->dest.workspace); FREE(assign->dest.workspace);
else if (assign->type == A_TO_OUTPUT)
FREE(assign->dest.output);
else if (assign->type == A_COMMAND) else if (assign->type == A_COMMAND)
FREE(assign->dest.command); FREE(assign->dest.command);
match_free(&(assign->match)); match_free(&(assign->match));

View File

@ -421,6 +421,11 @@ CFGFUN(bar_font, const char *font) {
current_bar.font = sstrdup(font); current_bar.font = sstrdup(font);
} }
CFGFUN(bar_separator_symbol, const char *separator) {
FREE(current_bar.separator_symbol);
current_bar.separator_symbol = sstrdup(separator);
}
CFGFUN(bar_mode, const char *mode) { CFGFUN(bar_mode, const char *mode) {
current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE)); current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE));
} }

View File

@ -778,15 +778,10 @@ static char *migrate_config(char *input, off_t size) {
/* write the whole config file to the pipe, the script will read everything /* write the whole config file to the pipe, the script will read everything
* immediately */ * immediately */
int written = 0; if (writeall(writepipe[1], input, size) == -1) {
int ret;
while (written < size) {
if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
warn("Could not write to pipe"); warn("Could not write to pipe");
return NULL; return NULL;
} }
written += ret;
}
close(writepipe[1]); close(writepipe[1]);
/* close writing end of the readpipe (connected to the scripts stdout) */ /* close writing end of the readpipe (connected to the scripts stdout) */
@ -795,7 +790,7 @@ static char *migrate_config(char *input, off_t size) {
/* read the scripts output */ /* read the scripts output */
int conv_size = 65535; int conv_size = 65535;
char *converted = malloc(conv_size); char *converted = malloc(conv_size);
int read_bytes = 0; int read_bytes = 0, ret;
do { do {
if (read_bytes == conv_size) { if (read_bytes == conv_size) {
conv_size += 65535; conv_size += 65535;
@ -823,11 +818,11 @@ static char *migrate_config(char *input, off_t size) {
fprintf(stderr, "Migration process exit code was != 0\n"); fprintf(stderr, "Migration process exit code was != 0\n");
if (returncode == 2) { if (returncode == 2) {
fprintf(stderr, "could not start the migration script\n"); fprintf(stderr, "could not start the migration script\n");
/* TODO: script was not found. tell the user to fix his system or create a v4 config */ /* TODO: script was not found. tell the user to fix their system or create a v4 config */
} else if (returncode == 1) { } else if (returncode == 1) {
fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n"); fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
fprintf(stderr, "# i3 config file (v4)\n"); fprintf(stderr, "# i3 config file (v4)\n");
/* TODO: nag the user with a message to include a hint for i3 in his config file */ /* TODO: nag the user with a message to include a hint for i3 in their config file */
} }
return NULL; return NULL;
} }

View File

@ -511,7 +511,7 @@ DRAGGING_CB(resize_window_callback) {
dest_height = max(dest_height, (int)(dest_width / ratio)); dest_height = max(dest_height, (int)(dest_width / ratio));
} }
con->rect = (Rect) {dest_x, dest_y, dest_width, dest_height}; con->rect = (Rect){dest_x, dest_y, dest_width, dest_height};
/* Obey window size */ /* Obey window size */
floating_check_size(con); floating_check_size(con);

View File

@ -106,7 +106,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
return; return;
} }
/* Focus the output on which the user moved his cursor */ /* Focus the output on which the user moved their cursor */
Con *old_focused = focused; Con *old_focused = focused;
Con *next = con_descend_focused(output_get_content(output->con)); Con *next = con_descend_focused(output_get_content(output->con));
/* Since we are switching outputs, this *must* be a different workspace, so /* Since we are switching outputs, this *must* be a different workspace, so
@ -149,7 +149,7 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
enter_child = true; enter_child = true;
} }
/* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ /* If not, then the user moved their cursor to the root window. In that case, we adjust c_ws */
if (con == NULL) { if (con == NULL) {
DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
check_crossing_screen_boundary(event->root_x, event->root_y); check_crossing_screen_boundary(event->root_x, event->root_y);
@ -874,12 +874,16 @@ static void handle_client_message(xcb_client_message_event_t *event) {
.root_y = y_root, .root_y = y_root,
.event_x = x_root - (con->rect.x), .event_x = x_root - (con->rect.x),
.event_y = y_root - (con->rect.y)}; .event_y = y_root - (con->rect.y)};
if (direction == _NET_WM_MOVERESIZE_MOVE) { switch (direction) {
case _NET_WM_MOVERESIZE_MOVE:
floating_drag_window(con->parent, &fake); floating_drag_window(con->parent, &fake);
} else if (direction >= _NET_WM_MOVERESIZE_SIZE_TOPLEFT && direction <= _NET_WM_MOVERESIZE_SIZE_LEFT) { break;
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
floating_resize_window(con->parent, FALSE, &fake); floating_resize_window(con->parent, FALSE, &fake);
} else { break;
default:
DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction); DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
break;
} }
} else { } else {
DLOG("unhandled clientmessage\n"); DLOG("unhandled clientmessage\n");
@ -1159,6 +1163,87 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat
return true; return true;
} }
/*
* Handles the _NET_WM_STRUT_PARTIAL property for allocating space for dock clients.
*
*/
static bool handle_strut_partial_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *prop) {
DLOG("strut partial change for window 0x%08x\n", window);
Con *con;
if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
return false;
}
if (prop == NULL) {
xcb_generic_error_t *err = NULL;
xcb_get_property_cookie_t strut_cookie = xcb_get_property(conn, false, window, A__NET_WM_STRUT_PARTIAL,
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX);
prop = xcb_get_property_reply(conn, strut_cookie, &err);
if (err != NULL) {
DLOG("got error when getting strut partial property: %d\n", err->error_code);
free(err);
return false;
}
if (prop == NULL) {
return false;
}
}
DLOG("That is con %p / %s\n", con, con->name);
window_update_strut_partial(con->window, prop);
/* we only handle this change for dock clients */
if (con->parent == NULL || con->parent->type != CT_DOCKAREA) {
return true;
}
Con *search_at = croot;
Con *output = con_get_output(con);
if (output != NULL) {
DLOG("Starting search at output %s\n", output->name);
search_at = output;
}
/* find out the desired position of this dock window */
if (con->window->reserved.top > 0 && con->window->reserved.bottom == 0) {
DLOG("Top dock client\n");
con->window->dock = W_DOCK_TOP;
} else if (con->window->reserved.top == 0 && con->window->reserved.bottom > 0) {
DLOG("Bottom dock client\n");
con->window->dock = W_DOCK_BOTTOM;
} else {
DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n");
if (con->geometry.y < (search_at->rect.height / 2)) {
DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n",
con->geometry.y, (search_at->rect.height / 2));
con->window->dock = W_DOCK_TOP;
} else {
DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n",
con->geometry.y, (search_at->rect.height / 2));
con->window->dock = W_DOCK_BOTTOM;
}
}
/* find the dockarea */
Con *dockarea = con_for_window(search_at, con->window, NULL);
assert(dockarea != NULL);
/* attach the dock to the dock area */
con_detach(con);
con->parent = dockarea;
TAILQ_INSERT_HEAD(&(dockarea->focus_head), con, focused);
TAILQ_INSERT_HEAD(&(dockarea->nodes_head), con, nodes);
tree_render();
return true;
}
/* Returns false if the event could not be processed (e.g. the window could not /* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */ * be found), true otherwise */
typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
@ -1177,7 +1262,8 @@ static struct property_handler_t property_handlers[] = {
{0, UINT_MAX, handle_clientleader_change}, {0, UINT_MAX, handle_clientleader_change},
{0, UINT_MAX, handle_transient_for}, {0, UINT_MAX, handle_transient_for},
{0, 128, handle_windowrole_change}, {0, 128, handle_windowrole_change},
{0, 128, handle_class_change}}; {0, 128, handle_class_change},
{0, UINT_MAX, handle_strut_partial_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/* /*
@ -1196,6 +1282,7 @@ void property_handlers_init(void) {
property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
property_handlers[6].atom = A_WM_WINDOW_ROLE; property_handlers[6].atom = A_WM_WINDOW_ROLE;
property_handlers[7].atom = XCB_ATOM_WM_CLASS; property_handlers[7].atom = XCB_ATOM_WM_CLASS;
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
} }
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@ -1315,7 +1402,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_motion_notify((xcb_motion_notify_event_t *)event); handle_motion_notify((xcb_motion_notify_event_t *)event);
break; break;
/* Enter window = user moved his mouse over the window */ /* Enter window = user moved their mouse over the window */
case XCB_ENTER_NOTIFY: case XCB_ENTER_NOTIFY:
handle_enter_notify((xcb_enter_notify_event_t *)event); handle_enter_notify((xcb_enter_notify_event_t *)event);
break; break;

View File

@ -157,7 +157,7 @@ static void dump_binding(yajl_gen gen, Binding *bind) {
y(integer, bind->keycode); y(integer, bind->keycode);
ystr("input_type"); ystr("input_type");
ystr((const char*)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse")); ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
ystr("symbol"); ystr("symbol");
if (bind->symbol == NULL) if (bind->symbol == NULL)
@ -397,7 +397,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr("transient_for"); ystr("transient_for");
if (con->window->transient_for == XCB_NONE) if (con->window->transient_for == XCB_NONE)
y(null); y(null);
else y(integer, con->window->transient_for); else
y(integer, con->window->transient_for);
y(map_close); y(map_close);
} }
@ -449,6 +450,10 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(array_open); y(array_open);
Match *match; Match *match;
TAILQ_FOREACH(match, &(con->swallow_head), matches) { TAILQ_FOREACH(match, &(con->swallow_head), matches) {
/* We will generate a new restart_mode match specification after this
* loop, so skip this one. */
if (match->restart_mode)
continue;
y(map_open); y(map_open);
if (match->dock != -1) { if (match->dock != -1) {
ystr("dock"); ystr("dock");
@ -593,6 +598,11 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
YSTR_IF_SET(status_command); YSTR_IF_SET(status_command);
YSTR_IF_SET(font); YSTR_IF_SET(font);
if (config->separator_symbol) {
ystr("separator_symbol");
ystr(config->separator_symbol);
}
ystr("workspace_buttons"); ystr("workspace_buttons");
y(bool, !config->hide_workspace_buttons); y(bool, !config->hide_workspace_buttons);

View File

@ -105,7 +105,7 @@ static int json_end_map(void *ctx) {
int cnt = 1; int cnt = 1;
while (workspace != NULL) { while (workspace != NULL) {
FREE(json_node->name); FREE(json_node->name);
asprintf(&(json_node->name), "%s_%d", base, cnt++); sasprintf(&(json_node->name), "%s_%d", base, cnt++);
workspace = NULL; workspace = NULL;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name)); GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, json_node->name));

View File

@ -502,7 +502,7 @@ int main(int argc, char *argv[]) {
} }
xcb_void_cookie_t cookie; xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]) {ROOT_EVENT_MASK}); cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
check_error(conn, cookie, "Another window manager seems to be running"); check_error(conn, cookie, "Another window manager seems to be running");
xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL); xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL);
@ -742,10 +742,10 @@ int main(int argc, char *argv[]) {
xcb_create_gc(conn, gc, root->root, xcb_create_gc(conn, gc, root->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE, 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}); (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_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_change_window_attributes_checked(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
xcb_flush(conn); xcb_flush(conn);
xcb_free_gc(conn, gc); xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap); xcb_free_pixmap(conn, pixmap);
@ -778,7 +778,7 @@ int main(int argc, char *argv[]) {
ELOG("Could not setup signal handler"); ELOG("Could not setup signal handler");
/* Ignore SIGPIPE to survive errors when an IPC client disconnects /* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending him a message */ * while we are sending them a message */
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
/* Autostarting exec-lines */ /* Autostarting exec-lines */

View File

@ -69,7 +69,7 @@ void restore_geometry(void) {
/* Strictly speaking, this line doesnt really belong here, but since we /* Strictly speaking, this line doesnt really belong here, but since we
* are syncing, lets un-register as a window manager first */ * are syncing, lets un-register as a window manager first */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]) {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}); xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT});
/* Make sure our changes reach the X server, we restart/exit now */ /* Make sure our changes reach the X server, we restart/exit now */
xcb_aux_sync(conn); xcb_aux_sync(conn);
@ -253,10 +253,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* See if any container swallows this new window */ /* See if any container swallows this new window */
nc = con_for_window(search_at, cwindow, &match); nc = con_for_window(search_at, cwindow, &match);
if (nc == NULL) { if (nc == NULL) {
/* If not, check if it is assigned to a specific workspace / output */ /* If not, check if it is assigned to a specific workspace */
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) { if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) {
DLOG("Assignment matches (%p)\n", match); DLOG("Assignment matches (%p)\n", match);
if (assignment->type == A_TO_WORKSPACE) {
Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL); Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
nc = con_descend_tiling_focused(assigned_ws); nc = con_descend_tiling_focused(assigned_ws);
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name); DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
@ -268,8 +267,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* set the urgency hint on the window if the workspace is not visible */ /* set the urgency hint on the window if the workspace is not visible */
if (!workspace_is_visible(assigned_ws)) if (!workspace_is_visible(assigned_ws))
urgency_hint = true; urgency_hint = true;
}
/* TODO: handle assignments with type == A_TO_OUTPUT */
} else if (startup_ws) { } else if (startup_ws) {
/* If its not assigned, but was started on a specific workspace, /* If its not assigned, but was started on a specific workspace,
* we want to open it there */ * we want to open it there */
@ -434,7 +431,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* which are not managed by the wm anyways). We store the original geometry * which are not managed by the wm anyways). We store the original geometry
* here because its used for dock clients. */ * here because its used for dock clients. */
if (nc->geometry.width == 0) if (nc->geometry.width == 0)
nc->geometry = (Rect) {geom->x, geom->y, geom->width, geom->height}; nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
if (motif_border_style != BS_NORMAL) { if (motif_border_style != BS_NORMAL) {
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style); DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
@ -455,7 +452,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
} }
/* explicitly set the border width to the default */ /* explicitly set the border width to the default */
if (nc->current_border_width == -1) {
nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width); nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width);
}
/* to avoid getting an UnmapNotify event due to reparenting, we temporarily /* to avoid getting an UnmapNotify event due to reparenting, we temporarily
* declare no interest in any state change event of this window */ * declare no interest in any state change event of this window */

View File

@ -24,3 +24,25 @@ Con *output_get_content(Con *output) {
return NULL; return NULL;
} }
/*
* Returns an 'output' corresponding to one of left/right/down/up or a specific
* output name.
*
*/
Output *get_output_from_string(Output *current_output, const char *output_str) {
Output *output;
if (strcasecmp(output_str, "left") == 0)
output = get_output_next_wrap(D_LEFT, current_output);
else if (strcasecmp(output_str, "right") == 0)
output = get_output_next_wrap(D_RIGHT, current_output);
else if (strcasecmp(output_str, "up") == 0)
output = get_output_next_wrap(D_UP, current_output);
else if (strcasecmp(output_str, "down") == 0)
output = get_output_next_wrap(D_DOWN, current_output);
else
output = get_output_by_name(output_str);
return output;
}

View File

@ -154,7 +154,7 @@ void render_con(Con *con, bool render_fullscreen) {
/* depending on the border style, the rect of the child window /* depending on the border style, the rect of the child window
* needs to be smaller */ * needs to be smaller */
Rect *inset = &(con->window_rect); Rect *inset = &(con->window_rect);
*inset = (Rect) {0, 0, con->rect.width, con->rect.height}; *inset = (Rect){0, 0, con->rect.width, con->rect.height};
if (!render_fullscreen) if (!render_fullscreen)
*inset = rect_add(*inset, con_border_style_rect(con)); *inset = rect_add(*inset, con_border_style_rect(con));

View File

@ -125,9 +125,9 @@ void restore_connect(void) {
static void update_placeholder_contents(placeholder_state *state) { static void update_placeholder_contents(placeholder_state *state) {
xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND,
(uint32_t[]) {config.client.placeholder.background}); (uint32_t[]){config.client.placeholder.background});
xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1,
(xcb_rectangle_t[]) {{0, 0, state->rect.width, state->rect.height}}); (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}});
// TODO: make i3font functions per-connection, at least these two for now…? // TODO: make i3font functions per-connection, at least these two for now…?
xcb_flush(restore_conn); xcb_flush(restore_conn);
@ -180,7 +180,9 @@ static void update_placeholder_contents(placeholder_state *state) {
static void open_placeholder_window(Con *con) { static void open_placeholder_window(Con *con) {
if (con_is_leaf(con) && if (con_is_leaf(con) &&
(con->window == NULL || con->window->id == XCB_NONE)) { (con->window == NULL || con->window->id == XCB_NONE) &&
!TAILQ_EMPTY(&(con->swallow_head)) &&
con->type == CT_CON) {
xcb_window_t placeholder = create_window( xcb_window_t placeholder = create_window(
restore_conn, restore_conn,
con->rect, con->rect,
@ -190,10 +192,15 @@ static void open_placeholder_window(Con *con) {
XCURSOR_CURSOR_POINTER, XCURSOR_CURSOR_POINTER,
true, true,
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
(uint32_t[]) { (uint32_t[]){
config.client.placeholder.background, config.client.placeholder.background,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
}); });
/* Make i3 not focus this window. */
xcb_icccm_wm_hints_t hints;
xcb_icccm_wm_hints_set_none(&hints);
xcb_icccm_wm_hints_set_input(&hints, 0);
xcb_icccm_set_wm_hints(restore_conn, placeholder, &hints);
/* Set the same name as was stored in the layout file. While perhaps /* Set the same name as was stored in the layout file. While perhaps
* slightly confusing in the first instant, this brings additional * slightly confusing in the first instant, this brings additional
* clarity to which placeholder is waiting for which actual window. */ * clarity to which placeholder is waiting for which actual window. */
@ -211,7 +218,7 @@ static void open_placeholder_window(Con *con) {
xcb_create_pixmap(restore_conn, root_depth, state->pixmap, xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
state->window, state->rect.width, state->rect.height); state->window, state->rect.width, state->rect.height);
state->gc = xcb_generate_id(restore_conn); state->gc = xcb_generate_id(restore_conn);
xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]) {0}); xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
update_placeholder_contents(state); update_placeholder_contents(state);
TAILQ_INSERT_TAIL(&state_head, state, state); TAILQ_INSERT_TAIL(&state_head, state, state);
@ -323,7 +330,7 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
xcb_create_pixmap(restore_conn, root_depth, state->pixmap, xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
state->window, state->rect.width, state->rect.height); state->window, state->rect.width, state->rect.height);
state->gc = xcb_generate_id(restore_conn); state->gc = xcb_generate_id(restore_conn);
xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]) {0}); xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
update_placeholder_contents(state); update_placeholder_contents(state);
xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,

View File

@ -70,8 +70,14 @@ static int backtrace(void) {
int stdin_pipe[2], int stdin_pipe[2],
stdout_pipe[2]; stdout_pipe[2];
pipe(stdin_pipe); if (pipe(stdin_pipe) == -1) {
pipe(stdout_pipe); ELOG("Failed to init stdin_pipe\n");
return -1;
}
if (pipe(stdout_pipe) == -1) {
ELOG("Failed to init stdout_pipe\n");
return -1;
}
/* close standard streams in case i3 is started from a terminal; gdb /* close standard streams in case i3 is started from a terminal; gdb
* needs to run without controlling terminal for it to work properly in * needs to run without controlling terminal for it to work properly in
@ -129,9 +135,9 @@ static int sig_draw_window(xcb_window_t win, int width, int height, int font_hei
/* re-draw the background */ /* re-draw the background */
xcb_rectangle_t border = {0, 0, width, height}, xcb_rectangle_t border = {0, 0, width, height},
inner = {2, 2, width - 4, height - 4}; inner = {2, 2, width - 4, height - 4};
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {get_colorpixel("#FF0000")}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]) {get_colorpixel("#000000")}); xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */ /* restore font color */

View File

@ -257,6 +257,22 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
} }
} }
/**
* Renames workspaces that are mentioned in the startup sequences.
*
*/
void startup_sequence_rename_workspace(char *old_name, char *new_name) {
struct Startup_Sequence *current;
TAILQ_FOREACH(current, &startup_sequences, sequences) {
if (strcmp(current->workspace, old_name) != 0)
continue;
DLOG("Renaming workspace \"%s\" to \"%s\" in startup sequence %s.\n",
old_name, new_name, current->id);
free(current->workspace);
current->workspace = sstrdup(new_name);
}
}
/** /**
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
* *

View File

@ -76,7 +76,7 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
/* TODO: refactor the following */ /* TODO: refactor the following */
croot = con_new(NULL, NULL); croot = con_new(NULL, NULL);
croot->rect = (Rect) { croot->rect = (Rect){
geometry->x, geometry->x,
geometry->y, geometry->y,
geometry->width, geometry->width,
@ -104,6 +104,8 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) {
TAILQ_INSERT_HEAD(&(croot->nodes_head), __i3, nodes); TAILQ_INSERT_HEAD(&(croot->nodes_head), __i3, nodes);
} }
restore_open_placeholder_windows(croot);
return true; return true;
} }
@ -118,7 +120,7 @@ void tree_init(xcb_get_geometry_reply_t *geometry) {
croot->name = "root"; croot->name = "root";
croot->type = CT_ROOT; croot->type = CT_ROOT;
croot->layout = L_SPLITH; croot->layout = L_SPLITH;
croot->rect = (Rect) { croot->rect = (Rect){
geometry->x, geometry->x,
geometry->y, geometry->y,
geometry->width, geometry->width,
@ -237,7 +239,7 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
* unmap the window, * unmap the window,
* then reparent it to the root window. */ * then reparent it to the root window. */
xcb_change_window_attributes(conn, con->window->id, xcb_change_window_attributes(conn, con->window->id,
XCB_CW_EVENT_MASK, (uint32_t[]) {XCB_NONE}); XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE});
xcb_unmap_window(conn, con->window->id); xcb_unmap_window(conn, con->window->id);
cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0); cookie = xcb_reparent_window(conn, con->window->id, root, 0, 0);

View File

@ -42,14 +42,14 @@ bool rect_contains(Rect rect, uint32_t x, uint32_t y) {
} }
Rect rect_add(Rect a, Rect b) { Rect rect_add(Rect a, Rect b) {
return (Rect) {a.x + b.x, return (Rect){a.x + b.x,
a.y + b.y, a.y + b.y,
a.width + b.width, a.width + b.width,
a.height + b.height}; a.height + b.height};
} }
Rect rect_sub(Rect a, Rect b) { Rect rect_sub(Rect a, Rect b) {
return (Rect) {a.x - b.x, return (Rect){a.x - b.x,
a.y - b.y, a.y - b.y,
a.width - b.width, a.width - b.width,
a.height - b.height}; a.height - b.height};
@ -265,25 +265,13 @@ char *store_restart_layout(void) {
return NULL; return NULL;
} }
size_t written = 0; if (writeall(fd, payload, length) == -1) {
while (written < length) { ELOG("Could not write restart layout to \"%s\", layout will be lost: %s\n", filename, strerror(errno));
int n = write(fd, payload + written, length - written);
/* TODO: correct error-handling */
if (n == -1) {
perror("write()");
free(filename); free(filename);
close(fd); close(fd);
return NULL; return NULL;
} }
if (n == 0) {
DLOG("write == 0?\n");
free(filename);
close(fd);
return NULL;
}
written += n;
DLOG("written: %zd of %zd\n", written, length);
}
close(fd); close(fd);
if (length > 0) { if (length > 0) {

View File

@ -189,7 +189,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop)
DLOG("Reserved pixels changed to: left = %d, right = %d, top = %d, bottom = %d\n", DLOG("Reserved pixels changed to: left = %d, right = %d, top = %d, bottom = %d\n",
strut[0], strut[1], strut[2], strut[3]); strut[0], strut[1], strut[2], strut[3]);
win->reserved = (struct reservedpx) {strut[0], strut[1], strut[2], strut[3]}; win->reserved = (struct reservedpx){strut[0], strut[1], strut[2], strut[3]};
free(prop); free(prop);
} }

View File

@ -879,3 +879,111 @@ Con *workspace_encapsulate(Con *ws) {
return new; return new;
} }
/**
* Move the given workspace to the specified output.
* This returns true if and only if moving the workspace was successful.
*/
bool workspace_move_to_output(Con *ws, char *name) {
LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name);
Con *current_output_con = con_get_output(ws);
if (!current_output_con) {
ELOG("Could not get the output container for workspace %p / %s.\n", ws, ws->name);
return false;
}
Output *current_output = get_output_by_name(current_output_con->name);
if (!current_output) {
ELOG("Cannot get current output. This is a bug in i3.\n");
return false;
}
Output *output = get_output_from_string(current_output, name);
if (!output) {
ELOG("Could not get output from string \"%s\"\n", name);
return false;
}
Con *content = output_get_content(output->con);
LOG("got output %p with content %p\n", output, content);
Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
bool workspace_was_visible = workspace_is_visible(ws);
if (con_num_children(ws->parent) == 1) {
LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
/* check if we can find a workspace assigned to this output */
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0)
continue;
/* check if this workspace is already attached to the tree */
Con *workspace = NULL, *out;
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
GREP_FIRST(workspace, output_get_content(out),
!strcasecmp(child->name, assignment->name));
if (workspace != NULL)
continue;
/* so create the workspace referenced to by this assignment */
LOG("Creating workspace from assignment %s.\n", assignment->name);
workspace_get(assignment->name, NULL);
used_assignment = true;
break;
}
/* if we couldn't create the workspace using an assignment, create
* it on the output */
if (!used_assignment)
create_workspace_on_output(current_output, ws->parent);
/* notify the IPC listeners */
ipc_send_workspace_event("init", ws, NULL);
}
DLOG("Detaching\n");
/* detach from the old output and attach to the new output */
Con *old_content = ws->parent;
con_detach(ws);
if (workspace_was_visible) {
/* The workspace which we just detached was visible, so focus
* the next one in the focus-stack. */
Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
workspace_show(focus_ws);
}
con_attach(ws, content, false);
/* fix the coordinates of the floating containers */
Con *floating_con;
TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
ipc_send_workspace_event("move", ws, NULL);
if (workspace_was_visible) {
/* Focus the moved workspace on the destination output. */
workspace_show(ws);
}
/* NB: We cannot simply work with previously_visible_ws since it might
* have been cleaned up by workspace_show() already, depending on the
* focus order/number of other workspaces on the output.
* Instead, we loop through the available workspaces and only work with
* previously_visible_ws if we still find it. */
TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
if (ws != previously_visible_ws)
continue;
/* Call the on_remove_child callback of the workspace which previously
* was visible on the destination output. Since it is no longer
* visible, it might need to get cleaned up. */
CALL(previously_visible_ws, on_remove_child);
break;
}
return true;
}

32
src/x.c
View File

@ -352,8 +352,8 @@ void x_draw_decoration(Con *con) {
Rect *r = &(con->rect); Rect *r = &(con->rect);
Rect *w = &(con->window_rect); Rect *w = &(con->window_rect);
p->con_rect = (struct width_height) {r->width, r->height}; p->con_rect = (struct width_height){r->width, r->height};
p->con_window_rect = (struct width_height) {w->width, w->height}; p->con_window_rect = (struct width_height){w->width, w->height};
p->con_deco_rect = con->deco_rect; p->con_deco_rect = con->deco_rect;
p->background = config.client.background; p->background = config.client.background;
p->con_is_leaf = con_is_leaf(con); p->con_is_leaf = con_is_leaf(con);
@ -403,7 +403,7 @@ void x_draw_decoration(Con *con) {
); );
#endif #endif
xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {config.client.background}); xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){config.client.background});
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, sizeof(background) / sizeof(xcb_rectangle_t), background);
} }
@ -424,7 +424,7 @@ void x_draw_decoration(Con *con) {
* (left, bottom and right part). We dont just fill the whole * (left, bottom and right part). We dont just fill the whole
* rectangle because some childs are not freely resizable and we want * rectangle because some childs are not freely resizable and we want
* their background color to "shine through". */ * their background color to "shine through". */
xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->background}); xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background});
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
xcb_rectangle_t leftline = {0, 0, br.x, r->height}; xcb_rectangle_t leftline = {0, 0, br.x, r->height};
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline); xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &leftline);
@ -450,12 +450,12 @@ void x_draw_decoration(Con *con) {
if (TAILQ_NEXT(con, nodes) == NULL && if (TAILQ_NEXT(con, nodes) == NULL &&
TAILQ_PREV(con, nodes_head, nodes) == NULL && TAILQ_PREV(con, nodes_head, nodes) == NULL &&
con->parent->type != CT_FLOATING_CON) { con->parent->type != CT_FLOATING_CON) {
xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->indicator}); xcb_change_gc(conn, con->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->indicator});
if (p->parent_layout == L_SPLITH) if (p->parent_layout == L_SPLITH)
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
{r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}}); {r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height}});
else if (p->parent_layout == L_SPLITV) else if (p->parent_layout == L_SPLITV)
xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, (xcb_rectangle_t[]){
{br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}}); {br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)}});
} }
} }
@ -466,16 +466,16 @@ void x_draw_decoration(Con *con) {
goto copy_pixmaps; goto copy_pixmaps;
/* 4: paint the bar */ /* 4: paint the bar */
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->background}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background});
xcb_rectangle_t drect = {con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height}; xcb_rectangle_t drect = {con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height};
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect); xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
/* 5: draw two unconnected horizontal lines in border color */ /* 5: draw two unconnected horizontal lines in border color */
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border});
Rect *dr = &(con->deco_rect); Rect *dr = &(con->deco_rect);
adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width; int deco_diff_l = borders_to_hide & ADJ_LEFT_SCREEN_EDGE ? 0 : con->current_border_width;
int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con-> current_border_width; int deco_diff_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width;
if (parent->layout == L_TABBED || if (parent->layout == L_TABBED ||
(parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) { (parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) {
deco_diff_l = 0; deco_diff_l = 0;
@ -545,12 +545,12 @@ after_title:
/* Draw a 1px separator line before and after every tab, so that tabs can /* Draw a 1px separator line before and after every tab, so that tabs can
* be easily distinguished. */ * be easily distinguished. */
if (parent->layout == L_TABBED) { if (parent->layout == L_TABBED) {
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border});
} else { } else {
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->background}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->background});
} }
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6, xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6,
(xcb_point_t[]) { (xcb_point_t[]){
{dr->x + dr->width, dr->y}, {dr->x + dr->width, dr->y},
{dr->x + dr->width, dr->y + dr->height}, {dr->x + dr->width, dr->y + dr->height},
{dr->x + dr->width - 1, dr->y}, {dr->x + dr->width - 1, dr->y},
@ -559,7 +559,7 @@ after_title:
{dr->x, dr->y}, {dr->x, dr->y},
}); });
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]) {p->color->border}); xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){p->color->border});
xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
copy_pixmaps: copy_pixmaps:
@ -975,9 +975,9 @@ void x_push_changes(Con *con) {
Output *target = get_output_containing(mid_x, mid_y); Output *target = get_output_containing(mid_x, mid_y);
if (current != target) { if (current != target) {
/* Ignore MotionNotify events generated by warping */ /* Ignore MotionNotify events generated by warping */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]) {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}); xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT});
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]) {ROOT_EVENT_MASK}); xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
} }
} }
warp_to = NULL; warp_to = NULL;

View File

@ -70,9 +70,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
*/ */
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) { uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]) {colorpixel}); xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel});
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2,
(xcb_point_t[]) {{x, y}, {to_x, to_y}}); (xcb_point_t[]){{x, y}, {to_x, to_y}});
} }
/* /*
@ -81,7 +81,7 @@ void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext
*/ */
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]) {colorpixel}); xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel});
xcb_rectangle_t rect = {x, y, width, height}; xcb_rectangle_t rect = {x, y, width, height};
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} }

View File

@ -56,7 +56,7 @@ void xcursor_load_cursors(void) {
*/ */
void xcursor_set_root_cursor(int cursor_id) { void xcursor_set_root_cursor(int cursor_id) {
xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, xcb_change_window_attributes(conn, root, XCB_CW_CURSOR,
(uint32_t[]) {xcursor_get_cursor(cursor_id)}); (uint32_t[]){xcursor_get_cursor(cursor_id)});
} }
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) { xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) {

View File

@ -227,7 +227,7 @@ if ($numtests == 1) {
END { cleanup() } END { cleanup() }
exit 0; exit ($aggregator->failed > 0);
# #
# Takes a test from the beginning of @testfiles and runs it. # Takes a test from the beginning of @testfiles and runs it.
@ -324,8 +324,9 @@ sub take_job {
} }
sub cleanup { sub cleanup {
my $exitcode = $?;
$_->() for our @CLEANUP; $_->() for our @CLEANUP;
exit; exit $exitcode;
} }
# must be in a begin block because we C<exit 0> above # must be in a begin block because we C<exit 0> above

View File

@ -109,7 +109,7 @@ sub activate_i3 {
if ($args{valgrind}) { if ($args{valgrind}) {
$i3cmd = $i3cmd =
qq|valgrind -v --log-file="$outdir/valgrind-for-$test.log" | . qq|valgrind --log-file="$outdir/valgrind-for-$test.log" | .
qq|--suppressions="./valgrind.supp" | . qq|--suppressions="./valgrind.supp" | .
qq|--leak-check=full --track-origins=yes --num-callers=20 | . qq|--leak-check=full --track-origins=yes --num-callers=20 | .
qq|--tool=memcheck -- $i3cmd|; qq|--tool=memcheck -- $i3cmd|;

View File

@ -15,6 +15,15 @@ my %ansi_line_upwards;
my $tests_total; my $tests_total;
sub noninteractive {
# CONTINUOUS_INTEGRATION gets set when running under Travis, see
# http://docs.travis-ci.com/user/ci-environment/ and
# https://github.com/travis-ci/travis-ci/issues/1337
return (! -t STDOUT) || (
defined($ENV{CONTINUOUS_INTEGRATION}) &&
$ENV{CONTINUOUS_INTEGRATION} eq 'true');
}
# setup %ansi_line_upwards to map all working displays to the # setup %ansi_line_upwards to map all working displays to the
# specific movement commands and initialize all status lines # specific movement commands and initialize all status lines
sub status_init { sub status_init {
@ -22,6 +31,8 @@ sub status_init {
my $displays = $args{displays}; my $displays = $args{displays};
$tests_total = $args{tests}; $tests_total = $args{tests};
return if noninteractive();
for my $n (1 .. @$displays) { for my $n (1 .. @$displays) {
# since we are moving upwards, get $display in reverse order # since we are moving upwards, get $display in reverse order
my $display = $displays->[-$n]; my $display = $displays->[-$n];
@ -41,6 +52,8 @@ sub status {
my ($display, $msg) = @_; my ($display, $msg) = @_;
my $status = "[$display] $msg"; my $status = "[$display] $msg";
return $status if noninteractive();
print print
$ansi_save_cursor, $ansi_save_cursor,
$ansi_line_upwards{$display}, $ansi_line_upwards{$display},
@ -53,6 +66,9 @@ sub status {
sub status_completed { sub status_completed {
my $num = shift; my $num = shift;
return if noninteractive();
print print
$ansi_save_cursor, $ansi_save_cursor,
$ansi_clear_line, $ansi_clear_line,

View File

@ -226,7 +226,7 @@ you might have to map it on your own and use this function:
sub wait_for_map { sub wait_for_map {
my ($win) = @_; my ($win) = @_;
my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win; my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
wait_for_event 2, sub { wait_for_event 4, sub {
$_[0]->{response_type} == MAP_NOTIFY and $_[0]->{window} == $id $_[0]->{response_type} == MAP_NOTIFY and $_[0]->{window} == $id
}; };
} }
@ -248,7 +248,7 @@ event.
sub wait_for_unmap { sub wait_for_unmap {
my ($win) = @_; my ($win) = @_;
# my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win; # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
wait_for_event 2, sub { wait_for_event 4, sub {
$_[0]->{response_type} == UNMAP_NOTIFY # and $_[0]->{window} == $id $_[0]->{response_type} == UNMAP_NOTIFY # and $_[0]->{window} == $id
}; };
sync_with_i3(); sync_with_i3();
@ -718,7 +718,7 @@ sub sync_with_i3 {
return $myrnd if $args{dont_wait_for_event}; return $myrnd if $args{dont_wait_for_event};
# now wait until the reply is here # now wait until the reply is here
return wait_for_event 2, sub { return wait_for_event 4, sub {
my ($event) = @_; my ($event) = @_;
# TODO: const # TODO: const
return 0 unless $event->{response_type} == 161; return 0 unless $event->{response_type} == 161;

View File

@ -143,6 +143,22 @@ wait_for_map $window;
@docked = get_dock_clients('top'); @docked = get_dock_clients('top');
is(@docked, 1, 'dock client on top'); is(@docked, 1, 'dock client on top');
# now change strut_partial to reserve space on the bottom and the dock should
# be moved to the bottom dock area
$x->change_property(
PROP_MODE_REPLACE,
$window->id,
$atomname->id,
$atomtype->id,
32, # 32 bit integer
12,
pack('L12', 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 1280, 0)
);
sync_with_i3;
@docked = get_dock_clients('bottom');
is(@docked, 1, 'dock client on bottom');
$window->destroy; $window->destroy;
wait_for_unmap $window; wait_for_unmap $window;

View File

@ -142,12 +142,17 @@ is_num_children($first_ws, 2, 'two containers on the first workspace');
complete_startup(); complete_startup();
sync_with_i3; sync_with_i3;
# even when renaming the workspace, windows should end up on the correct one
cmd "rename workspace $first_ws to temp";
# Startup has completed but the 30-second deletion time hasn't elapsed, # Startup has completed but the 30-second deletion time hasn't elapsed,
# so this window should still go on the leader's initial workspace. # so this window should still go on the leader's initial workspace.
$win = open_window({ dont_map => 1, client_leader => $leader }); $win = open_window({ dont_map => 1, client_leader => $leader });
$win->map; $win->map;
sync_with_i3; sync_with_i3;
cmd "rename workspace temp to $first_ws";
is_num_children($first_ws, 3, 'three containers on the first workspace'); is_num_children($first_ws, 3, 'three containers on the first workspace');
# Switch to the first workspace and move the focused window to the # Switch to the first workspace and move the focused window to the

View File

@ -156,7 +156,7 @@ is(parser_calls('move something to somewhere'),
'error for unknown literal ok'); 'error for unknown literal ok');
################################################################################ ################################################################################
# 3: Verify that escaping of double quotes works correctly # 3: Verify that escaping works correctly
################################################################################ ################################################################################
is(parser_calls('workspace "foo"'), is(parser_calls('workspace "foo"'),
@ -171,6 +171,18 @@ is(parser_calls('workspace "foo \"bar"'),
'cmd_workspace_name(foo "bar)', 'cmd_workspace_name(foo "bar)',
'Command with escaped double quotes ok'); 'Command with escaped double quotes ok');
is(parser_calls('workspace "foo \\'),
'cmd_workspace_name(foo \\)',
'Command with single backslash in the end ok');
is(parser_calls('workspace "foo\\\\bar"'),
'cmd_workspace_name(foo\\bar)',
'Command with escaped backslashes ok');
is(parser_calls('workspace "foo\\\\\\"bar"'),
'cmd_workspace_name(foo\\"bar)',
'Command with escaped double quotes after escaped backslashes ok');
################################################################################ ################################################################################
# 4: Verify that resize commands with a "px or ppt"-construction are parsed # 4: Verify that resize commands with a "px or ppt"-construction are parsed
# correctly # correctly

View File

@ -31,7 +31,7 @@ my $config = <<EOT;
# i3 config file (v4) # i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
force_display_urgency_hint 150ms force_display_urgency_hint 500ms
EOT EOT
my $pid = launch_with_config($config); my $pid = launch_with_config($config);
@ -70,7 +70,7 @@ is(@urgent, 1, 'window still marked as urgent');
# now check if the timer was triggered # now check if the timer was triggered
cmd "workspace $tmp2"; cmd "workspace $tmp2";
sleep(0.1); sleep(0.5);
@content = @{get_ws_content($tmp1)}; @content = @{get_ws_content($tmp1)};
@urgent = grep { $_->{urgent} } @content; @urgent = grep { $_->{urgent} } @content;
is(@urgent, 0, 'window not marked as urgent anymore'); is(@urgent, 0, 'window not marked as urgent anymore');
@ -142,7 +142,7 @@ cmd "workspace $tmp3";
$split_left->delete_hint('urgency'); $split_left->delete_hint('urgency');
sync_with_i3; sync_with_i3;
sleep(0.2); sleep(0.6);
is(count_total_urgent(get_ws($tmp3)), 0, "no more urgent windows on workspace $tmp3"); is(count_total_urgent(get_ws($tmp3)), 0, "no more urgent windows on workspace $tmp3");
exit_gracefully($pid); exit_gracefully($pid);

View File

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

View File

@ -1,52 +0,0 @@
#!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)
#
# Test that `workspace {N}` acts like `workspace number {N}` when N is a plain
# digit, and likewise for `move to workspace {N}`.
# Ticket: #1238
# Bug still in: 4.8-16-g3f5a0f0
use i3test;
cmd 'workspace 5:foo';
open_window;
fresh_workspace;
cmd 'workspace 5';
is(focused_ws, '5:foo',
'a command to switch to a workspace with a bare number should switch to a workspace of that number');
fresh_workspace;
my $win = open_window;
cmd '[id="' . $win->{id} . '"] move to workspace 5';
is(@{get_ws('5:foo')->{nodes}}, 2,
'a command to move a container to a workspace with a bare number should move that container to a workspace of that number');
fresh_workspace;
cmd 'workspace 7';
open_window;
cmd 'workspace 7:foo';
$win = open_window;
cmd 'workspace 7';
is(focused_ws, '7',
'a workspace with a name that is a matching plain number should be preferred when switching');
cmd '[id="' . $win->{id} . '"] move to workspace 7';
is(@{get_ws('7')->{nodes}}, 2,
'a workspace with a name that is a matching plain number should be preferred when moving');
done_testing;

View File

@ -0,0 +1,90 @@
#!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 workspaces are moved to the assigned output if they
# are renamed to an assigned name.
# Ticket: #1473
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
workspace 1 output fake-0
workspace 2 output fake-1
workspace 3:foo output fake-1
workspace baz output fake-1
EOT
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path());
$i3->connect->recv;
# Returns the name of the output on which this workspace resides
sub get_output_for_workspace {
my $ws_name = shift @_;
foreach (grep { not $_->{name} =~ /^__/ } @{$i3->get_tree->recv->{nodes}}) {
my $output = $_->{name};
foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
return $output if $_->{nodes}[0]->{name} =~ $ws_name;
}
}
}
##########################################################################
# Renaming the workspace to an unassigned name does not move the workspace
# (regression test)
##########################################################################
cmd 'focus output fake-0';
cmd 'rename workspace to unassigned';
is(get_output_for_workspace('unassigned'), 'fake-0',
'Unassigned workspace should stay on its output when being renamed');
##########################################################################
# Renaming a workspace by number only triggers the assignment
##########################################################################
cmd 'focus output fake-0';
cmd 'rename workspace to 2';
is(get_output_for_workspace('2'), 'fake-1',
'Renaming the workspace to a number should move it to the assigned output');
##########################################################################
# Renaming a workspace by number and name triggers the assignment
##########################################################################
cmd 'focus output fake-0';
cmd 'rename workspace to "2:foo"';
is(get_output_for_workspace('2:foo'), 'fake-1',
'Renaming the workspace to a number and name should move it to the assigned output');
##########################################################################
# Renaming a workspace by name only triggers the assignment
##########################################################################
cmd 'focus output fake-0';
cmd 'rename workspace to baz';
is(get_output_for_workspace('baz'), 'fake-1',
'Renaming the workspace to a number and name should move it to the assigned output');
exit_gracefully($pid);
done_testing;