Merge branch 'next' into master

This commit is contained in:
Michael Stapelberg 2019-08-03 15:14:52 +02:00
commit ea00565ad3
103 changed files with 2680 additions and 1248 deletions

View File

@ -1,8 +1,4 @@
sudo: false
dist: trusty
# TODO: remove “group” once trusty kernel is no longer affected by
# https://github.com/google/sanitizers/issues/837
group: deprecated-2017Q3
services:
- docker
language: c

View File

@ -1 +1 @@
4.16.1-non-git
4.17-non-git

View File

@ -118,7 +118,7 @@ EXTRA_DIST = \
I3_VERSION \
LICENSE \
PACKAGE-MAINTAINER \
RELEASE-NOTES-4.16.1 \
RELEASE-NOTES-4.17 \
generate-command-parser.pl \
parser-specs/commands.spec \
parser-specs/config.spec \
@ -277,6 +277,7 @@ i3_LDADD = \
libi3_CFLAGS = \
$(AM_CFLAGS) \
$(GLIBGOBJECT_CFLAGS) \
$(XCB_CFLAGS) \
$(XCB_UTIL_CFLAGS) \
$(XCB_UTIL_XRM_CFLAGS) \
@ -285,6 +286,7 @@ libi3_CFLAGS = \
libi3_LIBS = \
$(top_builddir)/libi3.a \
$(GLIBGOBJECT_LIBS) \
$(XCB_LIBS) \
$(XCB_UTIL_LIBS) \
$(XCB_UTIL_XRM_LIBS) \

96
RELEASE-NOTES-4.17 Normal file
View File

@ -0,0 +1,96 @@
┌────────────────────────────┐
│ Release notes for i3 v4.17 │
└────────────────────────────┘
This is i3 v4.17. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
This release contains a number of assorted fixes and improvements across pretty
much all individual components of i3.
┌────────────────────────────┐
│ Changes in i3 v4.17 │
└────────────────────────────┘
• config: make binding modes case-sensitive
• default config: mention ~/.config/i3/config
• default config: start xss-lock, nm-applet, pactl (volume keys)
• docs/userguide: update syntax in strip_workspace_*
• docs/userguide: add a section about hidpi displays
• docs/userguide: document mark --replace
• docs/userguide: uncomment and update mark section example
• docs/userguide: point out differences of normal/pixel title bars
• docs/userguide: clarify which config directives can be used at runtime
• docs/userguide: for_window is a directive, not a command
• docs/ipc: clarify event/reply types
• docs/ipc: mention new i3-ipc++ C++ library
• docs/ipc: clarify restart/exit behavior
• docs/i3bar-protocol: add markup
• man/i3.man: fix config file search order
• ipc: make restart command send a reply once restart completed
• ipc: use queue for all messages
fixes i3bar issues when switching between workspaces with many windows
• i3-dump-log: clarify log message
• i3-msg: exit with status code 2 when i3 returns an error
• render left and right borders of titles in stacked mode
• make swap work with floating windows, fix swap crash
• switch to clang-format-6.0
• add input and bounding shapes support
(e.g. for the https://github.com/phw/peek screen recorder)
• preserve back_and_forth across restarts
• allow partial UTF-8 to UCS-2 conversion for better handling of
title bar content which cannot be represented (e.g. emoji)
when using bitmap pixel fonts
• check for duplicate key bindings in i3 -C
• i3bar: support transparency via --transparency flag (RGBA)
• i3bar: support for user-defined border widths
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• build: correctly depend on glib (for g_utf8_make_valid)
• build: fix build when git is configured to show signatures
• ipc: report correct workspace in init event after workspace move
• ipc: send missing window:focus event
• i3bar: correctly recognize click events with text alignment
• i3bar: fix running without fd 0
• i3bar: correctly handle button presses on separator
• i3 --moreversion: warn when $DISPLAY is unset
• i3bar: support disabling click events
• release.sh: persist correct version number in docs
• accept output names containing spaces (e.g. in assignment)
• fix cursor resizing positioning
• fix aspect ratio issues (e.g. with mpv)
• fix brief focus flicker when renaming workspaces
• fix crash when canceling i3 via ctrl+c
• fix heap-use-after-free, memory leak
• fix focus bugs in enabling/disabling RandR outputs
• fix crash with popups when fullscreen is non-leaf
• fix crash when moving a second window to mark
• fix crash with programs with splash screen
• fix atoms when closing inactive workspace
• apply title_align to non-leaf containers
• layout loading: correctly mark non-leaf containers
• truncate wm_name utf8 strings to first zero byte
(makes window titles work with buggy clients)
• fix crash in workspace moving
• export I3SOCK environment variable (again)
• fix hanging flaky testcase by using the correct X11 connection
• resize: add missing error replies
• dont pop up floating windows on the wrong workspace
• remove extra \n from errx and die calls
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
aksel, Albert Safin, Alejandro Angulo, Christopher Hasse, Connor E, Hamish
Macdonald, Ingo Bürk, Iskustvo, Jeffrey Huxen, Jeremy Klotz, Jonathan
Woodlief, lasers, Morten Linderud, nejni-marji, Nguyễn Thái Ngọc Duy, Nils
ANDRÉ-CHANG, Oliver Kraitschy, Orestis Floros, TAL, Vladimir Panteleev
-- Michael Stapelberg, 2019-08-03

View File

@ -2,7 +2,7 @@
# Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69])
AC_INIT([i3], [4.16.1], [https://github.com/i3/i3/issues])
AC_INIT([i3], [4.17], [https://github.com/i3/i3/issues])
# For AX_EXTEND_SRCDIR
AX_ENABLE_BUILDDIR
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
@ -32,7 +32,7 @@ AX_EXTEND_SRCDIR
AS_IF([test -d ${srcdir}/.git],
[
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} rev-list --format=%cd --date=short -n1 $(git rev-parse HEAD) | tail -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
# Mirrors what libi3/is_debug_build.c does:
is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
],
@ -91,7 +91,7 @@ AX_PTHREAD
dnl Each prefix corresponds to a source tarball which users might have
dnl downloaded in a newer version and would like to overwrite.
PKG_CHECK_MODULES([LIBSN], [libstartup-notification-1.0])
PKG_CHECK_MODULES([XCB], [xcb xcb-xkb xcb-xinerama xcb-randr])
PKG_CHECK_MODULES([XCB], [xcb xcb-xkb xcb-xinerama xcb-randr xcb-shape])
PKG_CHECK_MODULES([XCB_UTIL], [xcb-event xcb-util])
PKG_CHECK_MODULES([XCB_UTIL_CURSOR], [xcb-cursor])
PKG_CHECK_MODULES([XCB_UTIL_KEYSYMS], [xcb-keysyms])
@ -101,6 +101,7 @@ PKG_CHECK_MODULES([XKBCOMMON], [xkbcommon xkbcommon-x11])
PKG_CHECK_MODULES([YAJL], [yajl])
PKG_CHECK_MODULES([LIBPCRE], [libpcre >= 8.10])
PKG_CHECK_MODULES([PANGOCAIRO], [cairo >= 1.14.4 pangocairo])
PKG_CHECK_MODULES([GLIBGOBJECT], [glib-2.0 gobject-2.0])
# Checks for programs.
AC_PROG_AWK

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.16.1-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Sun, 04 Nov 2018 14:47:25 +0100
i3-wm (4.16-1) unstable; urgency=medium
* New upstream release.

1
debian/control vendored
View File

@ -13,6 +13,7 @@ Build-Depends: debhelper (>= 9),
libxcb-cursor-dev,
libxcb-xrm-dev,
libxcb-xkb-dev,
libxcb-shape0-dev,
libxkbcommon-dev (>= 0.4.0),
libxkbcommon-x11-dev (>= 0.4.0),
asciidoc (>= 8.4.4),

View File

@ -141,6 +141,18 @@ background::
Overrides the background color for this particular block.
border::
Overrides the border color for this particular block.
border_top::
Defines the width (in pixels) of the top border of this block. Defaults
to 1.
border_right::
Defines the width (in pixels) of the right border of this block. Defaults
to 1.
border_bottom::
Defines the width (in pixels) of the bottom border of this block. Defaults
to 1.
border_left::
Defines the width (in pixels) of the left border of this block. Defaults
to 1.
min_width::
The minimum width (in pixels) of the block. If the content of the
+full_text+ key take less space than the specified min_width, the block
@ -215,13 +227,18 @@ An example of a block which uses all possible entries follows:
"color": "#00ff00",
"background": "#1c1c1c",
"border": "#ee0000",
"border_top": 1,
"border_right": 0,
"border_bottom": 3,
"border_left": 1,
"min_width": 300,
"align": "right",
"urgent": false,
"name": "ethernet",
"instance": "eth0",
"separator": true,
"separator_block_width": 9
"separator_block_width": 9,
"markup": "none"
}
------------------------------------------

View File

@ -80,7 +80,8 @@ Or, as a hexdump:
------------------------------------------------------------------------------
To generate and send such a message, you could use the following code in Perl:
------------------------------------------------------------
-------------------------------------------------------------------------------
sub format_ipc_command {
my ($msg) = @_;
my $len;
@ -90,7 +91,7 @@ sub format_ipc_command {
}
$sock->write(format_ipc_command("exit"));
------------------------------------------------------------------------------
-------------------------------------------------------------------------------
== Receiving replies from i3
@ -138,6 +139,20 @@ The reply consists of a list of serialized maps for each command that was
parsed. Each has the property +success (bool)+ and may also include a
human-readable error message in the property +error (string)+.
NOTE: When sending the `restart` command, you will get a singular reply once the
restart completed. All IPC connection states (e.g. subscriptions) will reset and
all but one socket will be closed. Libraries must be able to cope with this by
aligning their internal states. It is also recommended that libraries close
the last remaining socket(one which replied to `restart` command) to achieve
the full reset.
NOTE: It is easiest to always send the `restart` command alone: due to i3s
state reset, the reply messages of preceding commands are lost, and following
commands will not be executed.
NOTE: When processing the `exit` command, i3 will immediately exit without
sending a reply. Expect the socket to be shut down.
*Example:*
-------------------
[{ "success": true }]
@ -484,7 +499,7 @@ JSON dump:
}
]
}
------------------------
-----------------------
[[_marks_reply]]
=== MARKS reply
@ -681,9 +696,11 @@ responded to.
To get informed when certain things happen in i3, clients can subscribe to
events. Events consist of a name (like "workspace") and an event reply type
(like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format
as replies to specific commands. However, the highest bit of the message type
is set to 1 to indicate that this is an event reply instead of a normal reply.
(like I3_IPC_EVENT_WORKSPACE). Events sent by i3 follow a format similar to
replies but with the highest bit of the message type set to 1 to indicate an
event reply instead of a normal reply. Note that event types and reply types
do not follow the same enumeration scheme (e.g. event type 0 corresponds to the
workspace event however reply type 0 corresponds to the COMMAND reply).
Caveat: As soon as you subscribe to an event, it is not guaranteed any longer
that the requests to i3 are processed in order. This means, the following
@ -768,7 +785,7 @@ This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent", "reload", "rename", "restored", "move"). A
+current (object)+ property will be present with the affected workspace
whenever the type of event affects a workspace (otherwise, it will be +null).
whenever the type of event affects a workspace (otherwise, it will be +null+).
When the change is "focus", an +old (object)+ property will be present with the
previous workspace. When the first switch occurs (when i3 focuses the
@ -948,6 +965,7 @@ C::
* i3 includes a headerfile +i3/ipc.h+ which provides you all constants.
* https://github.com/acrisci/i3ipc-glib
C++::
* https://github.com/Iskustvo/i3-ipcpp[i3-ipc++]
* https://github.com/drmgc/i3ipcpp
Go::
* https://github.com/mdirkse/i3ipc-go

View File

@ -603,6 +603,9 @@ This option determines which border style new windows will have. The default is
+normal+. Note that default_floating_border applies only to windows which are starting out as
floating windows, e.g., dialog windows, but not windows that are floated later on.
Setting border style to +pixel+ eliminates title bars. The border style +normal+ allows you to
adjust edge border width while keeping your title bar.
*Syntax*:
---------------------------------------------
default_border normal|none|pixel
@ -654,7 +657,7 @@ hide_edge_borders vertical
[[for_window]]
=== Arbitrary commands for specific windows (for_window)
With the +for_window+ command, you can let i3 execute any command when it
With the +for_window+ directive, you can let i3 execute any command when it
encounters a specific window. This can be used to set windows to floating or to
change their border style, for example.
@ -677,7 +680,8 @@ for_window [class="urxvt"] border pixel 1
for_window [title="x200: ~/work"] floating enable
------------------------------------------------
The valid criteria are the same as those for commands, see <<command_criteria>>.
The valid criteria are the same as those for commands, see <<command_criteria>>. Only config
directives with a command equivalent can be executed at runtime, see <<list_of_commands>>.
[[no_focus]]
=== Don't focus window upon opening
@ -816,7 +820,8 @@ assign [class="^URxvt$"] → work
# Assign to the workspace with number 2, regardless of name
assign [class="^URxvt$"] → number 2
# You can also specify a number + name. If the workspace with number 2 exists, assign will skip the text part.
# You can also specify a number + name. If the workspace with number 2 exists,
# assign will skip the text part.
assign [class="^URxvt$"] → number "2: work"
# Start urxvt -name irssi
@ -1627,14 +1632,16 @@ buttons. This is useful if you want to have a named workspace that stays in
order on the bar according to its number without displaying the number prefix.
When +strip_workspace_numbers+ is set to +yes+, any workspace that has a name of
the form "[n]:[NAME]" will display only the name. You could use this, for
the form "[n][:][NAME]" will display only the name. You could use this, for
instance, to display Roman numerals rather than digits by naming your
workspaces to "1:I", "2:II", "3:III", "4:IV", ...
When +strip_workspace_name+ is set to +yes+, any workspace that has a name of
the form "[n]:[NAME]" will display only the number.
the form "[n][:][NAME]" will display only the number.
The default is to display the full name within the workspace button.
The default is to display the full name within the workspace button. Be aware
that the colon in the workspace name is optional, so `[n][NAME]` will also
have the the workspace name and number stripped correctly.
*Syntax*:
------------------------------
@ -1737,6 +1744,26 @@ bar {
}
--------------------------------------
=== Transparency
i3bar can support transparency by passing the +--transparency+ flag in the
configuration:
*Syntax*:
--------------------------------------
bar {
i3bar_command i3bar --transparency
}
--------------------------------------
In the i3bar color configuration and i3bar status block color attribute you can
then use colors in the RGBA format, i.e. the last two (hexadecimal) digits
specify the opacity. For example, +#00000000+ will be completely transparent,
while +#000000FF+ will be a fully opaque black (the same as +#000000+).
Please note that due to the way the tray specification works, enabling this
flag will cause all tray icons to have a transparent background.
[[list_of_commands]]
== List of commands
@ -1840,6 +1867,9 @@ The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
information on how to use them.
Note that config directives listed under <<configuring>> cannot be changed at runtime
unless they happen to have a command equivalent.
[[exec]]
=== Executing applications (exec)
@ -2105,8 +2135,7 @@ using one of the following methods:
+mark+:: A container with the specified mark, see <<vim_like_marks>>.
Note that swapping does not work with all containers. Most notably, swapping
floating containers or containers that have a parent-child relationship to one
another does not work.
containers that have a parent-child relationship to one another does not work.
*Syntax*:
----------------------------------------
@ -2414,8 +2443,9 @@ this mark or add it otherwise. Note that you may need to use this in
combination with +--add+ (see below) as any other marks will otherwise be
removed.
By default, a window can only have one mark. You can use the +--add+ flag to
put more than one mark on a window.
The +--replace+ flag causes i3 to remove any existing marks, which is also the
default behavior. You can use the +--add+ flag to put more than one mark on a
window.
Refer to <<show_marks>> if you don't want marks to be shown in the window decoration.
@ -2426,6 +2456,21 @@ mark [--add|--replace] [--toggle] <identifier>
unmark <identifier>
----------------------------------------------
You can use +i3-input+ to prompt for a mark name, then use the +mark+
and +focus+ commands to create and jump to custom marks:
*Examples*:
---------------------------------------
# read 1 character and mark the current window with this character
bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
# read 1 character and go to the window with the character
bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
---------------------------------------
Alternatively, if you do not want to mess with +i3-input+, you could create
separate bindings for a specific set of labels and then only use those labels:
*Example (in a terminal)*:
---------------------------------------------------------
# marks the focused container
@ -2441,21 +2486,6 @@ unmark irssi
[class="(?i)firefox"] unmark
---------------------------------------------------------
///////////////////////////////////////////////////////////////////
TODO: make i3-input replace %s
*Examples*:
---------------------------------------
# Read 1 character and mark the current window with this character
bindsym $mod+m exec i3-input -F 'mark %s' -l 1 -P 'Mark: '
# Read 1 character and go to the window with the character
bindsym $mod+g exec i3-input -F '[con_mark="%s"] focus' -l 1 -P 'Goto: '
---------------------------------------
Alternatively, if you do not want to mess with +i3-input+, you could create
separate bindings for a specific set of labels and then only use those labels.
///////////////////////////////////////////////////////////////////
[[pango_markup]]
=== Window title format
@ -2840,3 +2870,20 @@ and you are in multi-monitor mode (see <<multi_monitor>>).
Because i3 is not a compositing window manager, there is no ability to
display a window on two screens at the same time. Instead, your presentation
software needs to do this job (that is, open a window on each screen).
[[hidpi]]
=== High-resolution displays (aka HIDPI displays)
See https://wiki.archlinux.org/index.php/HiDPI for details on how to enable
scaling in various parts of the Linux desktop. i3 will read the desired DPI from
the `Xft.dpi` property. The property defaults to 96 DPI, so to achieve 200%
scaling, youd set `Xft.dpi: 192` in `~/.Xresources`.
If you are a long-time i3 user who just got a new monitor, double-check that:
* You are using a scalable font (starting with “pango:”) in your i3 config.
* You are using a terminal emulator which supports scaling. You could
temporarily switch to gnome-terminal, which is known to support scaling out of
the box, until you figure out how to adjust the font size in your favorite
terminal emulator.

View File

@ -17,12 +17,21 @@ font pango:monospace 8
# text rendering and scalability on retina/hidpi displays (thanks to pango).
#font pango:DejaVu Sans Mono 8
# 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
# The font above is very space-efficient, that is, it looks good, sharp and
# clear in small sizes. However, its unicode glyph coverage is limited, the old
# X core fonts rendering does not support right-to-left and this being a bitmap
# font, it doesn't scale on retina/hidpi displays.
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
# they are included here as an example. Modify as you see fit.
# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the
# screen before suspend.
exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork
# NetworkManager is the most popular way to manage wireless networks on Linux,
# and nm-applet is a desktop environment-independent system tray GUI for it.
exec --no-startup-id nm-applet
# Use pactl to adjust volume in PulseAudio.
bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10%
bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10%
bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
# use these keys for focus, movement, and resize directions when reaching for
# the arrows is not convenient
@ -187,7 +196,8 @@ bar {
# keysym-based config which used their favorite modifier (alt or windows)
#
# i3-config-wizard will not launch if there already is a config file
# in ~/.i3/config.
# in ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) or
# ~/.i3/config.
#
# Please remove the following exec line:
#######################################################################

View File

@ -18,12 +18,21 @@ font pango:monospace 8
# text rendering and scalability on retina/hidpi displays (thanks to pango).
#font pango:DejaVu Sans Mono 8
# 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
# The font above is very space-efficient, that is, it looks good, sharp and
# clear in small sizes. However, its unicode glyph coverage is limited, the old
# X core fonts rendering does not support right-to-left and this being a bitmap
# font, it doesnt scale on retina/hidpi displays.
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
# they are included here as an example. Modify as you see fit.
# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the
# screen before suspend.
exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork
# NetworkManager is the most popular way to manage wireless networks on Linux,
# and nm-applet is a desktop environment-independent system tray GUI for it.
exec --no-startup-id nm-applet
# Use pactl to adjust volume in PulseAudio.
bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10%
bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10%
bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
# Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod

View File

@ -822,7 +822,7 @@ int main(int argc, char *argv[]) {
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
errx(1, "Cannot open display\n");
errx(1, "Cannot open display");
if (xkb_x11_setup_xkb_extension(conn,
XKB_X11_MIN_MAJOR_XKB_VERSION,
@ -859,7 +859,7 @@ int main(int argc, char *argv[]) {
root = root_screen->root;
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
errx(EXIT_FAILURE, "Could not get modifier mapping\n");
errx(EXIT_FAILURE, "Could not get modifier mapping");
xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
@ -899,7 +899,7 @@ int main(int argc, char *argv[]) {
do { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
if (!reply) \
errx(EXIT_FAILURE, "Could not get atom " #name "\n"); \
errx(EXIT_FAILURE, "Could not get atom " #name); \
\
A_##name = reply->atom; \
free(reply); \

View File

@ -155,7 +155,7 @@ int main(int argc, char *argv[]) {
exit(1);
}
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled. Enabling SHM log until cancelled\n\n");
fprintf(stderr, "i3-dump-log: i3 is running, but SHM logging is not enabled. Enabling SHM log now while i3-dump-log is running\n\n");
ipcfd = ipc_connect(NULL);
const char *enablecmd = "debuglog on; shmlog 5242880";
if (ipc_send_message(ipcfd, strlen(enablecmd),

View File

@ -435,7 +435,7 @@ int main(int argc, char *argv[]) {
int screen;
conn = xcb_connect(NULL, &screen);
if (!conn || xcb_connection_has_error(conn))
die("Cannot open display\n");
die("Cannot open display");
sockfd = ipc_connect(socket_path);

View File

@ -67,6 +67,7 @@ typedef struct reply_t {
char *errorposition;
} reply_t;
static int exit_code = 0;
static reply_t last_reply;
static int reply_boolean_cb(void *params, int val) {
@ -76,8 +77,8 @@ static int reply_boolean_cb(void *params, int val) {
}
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
char *str = scalloc(len + 1, 1);
strncpy(str, (const char *)val, len);
char *str = sstrndup((const char *)val, len);
if (strcmp(last_key, "error") == 0)
last_reply.error = str;
else if (strcmp(last_key, "input") == 0)
@ -100,14 +101,14 @@ static int reply_end_map_cb(void *params) {
fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
}
fprintf(stderr, "ERROR: %s\n", last_reply.error);
exit_code = 2;
}
return 1;
}
static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
free(last_key);
last_key = scalloc(keyLen + 1, 1);
strncpy(last_key, (const char *)keyVal, keyLen);
last_key = sstrndup((const char *)keyVal, keyLen);
return 1;
}
@ -126,8 +127,7 @@ static yajl_callbacks reply_callbacks = {
static char *config_last_key = NULL;
static int config_string_cb(void *params, const unsigned char *val, size_t len) {
char *str = scalloc(len + 1, 1);
strncpy(str, (const char *)val, len);
char *str = sstrndup((const char *)val, len);
if (strcmp(config_last_key, "config") == 0) {
fprintf(stdout, "%s", str);
}
@ -144,8 +144,7 @@ static int config_end_map_cb(void *params) {
}
static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
config_last_key = scalloc(keyLen + 1, 1);
strncpy(config_last_key, (const char *)keyVal, keyLen);
config_last_key = sstrndup((const char *)keyVal, keyLen);
return 1;
}
@ -326,5 +325,5 @@ int main(int argc, char *argv[]) {
close(sockfd);
return 0;
return exit_code;
}

View File

@ -418,7 +418,7 @@ int main(int argc, char *argv[]) {
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
die("Cannot open display\n");
die("Cannot open display");
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
@ -512,7 +512,7 @@ int main(int argc, char *argv[]) {
do { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
if (!reply) \
die("Could not get atom " #name "\n"); \
die("Could not get atom " #name); \
\
A_##name = reply->atom; \
free(reply); \

View File

@ -61,6 +61,10 @@ struct status_block {
bool urgent;
bool no_separator;
uint32_t border_top;
uint32_t border_right;
uint32_t border_bottom;
uint32_t border_left;
bool pango_markup;
/* The amount of pixels necessary to render a separater after the block. */

View File

@ -48,6 +48,7 @@ typedef struct config_t {
position_t position;
bool verbose;
bool transparency;
struct xcb_color_strings_t colors;
bool disable_binding_mode_indicator;
bool disable_ws;

View File

@ -35,6 +35,7 @@ i3bar_child child;
/* stdin- and SIGCHLD-watchers */
ev_io *stdin_io;
int stdin_fd;
ev_child *child_sig;
/* JSON parser for stdin */
@ -175,6 +176,12 @@ static int stdin_start_map(void *context) {
else
ctx->block.sep_block_width = logical_px(8) + separator_symbol_width;
/* By default we draw all four borders if a border is set. */
ctx->block.border_top = 1;
ctx->block.border_right = 1;
ctx->block.border_bottom = 1;
ctx->block.border_left = 1;
return 1;
}
@ -261,6 +268,22 @@ static int stdin_integer(void *context, long long val) {
ctx->block.sep_block_width = (uint32_t)val;
return 1;
}
if (strcasecmp(ctx->last_map_key, "border_top") == 0) {
ctx->block.border_top = (uint32_t)val;
return 1;
}
if (strcasecmp(ctx->last_map_key, "border_right") == 0) {
ctx->block.border_right = (uint32_t)val;
return 1;
}
if (strcasecmp(ctx->last_map_key, "border_bottom") == 0) {
ctx->block.border_bottom = (uint32_t)val;
return 1;
}
if (strcasecmp(ctx->last_map_key, "border_left") == 0) {
ctx->block.border_left = (uint32_t)val;
return 1;
}
return 1;
}
@ -450,7 +473,7 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
}
free(buffer);
ev_io_stop(main_loop, stdin_io);
ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
}
@ -562,17 +585,17 @@ void start_child(char *command) {
close(pipe_in[1]);
close(pipe_out[0]);
dup2(pipe_in[0], STDIN_FILENO);
stdin_fd = pipe_in[0];
child_stdin = pipe_out[1];
break;
}
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
stdin_io = smalloc(sizeof(ev_io));
ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
/* We must cleanup, if the child unexpectedly terminates */

View File

@ -56,10 +56,11 @@ static char *expand_path(char *path) {
}
static void print_usage(char *elf_name) {
printf("Usage: %s -b bar_id [-s sock_path] [-h] [-v]\n", elf_name);
printf("Usage: %s -b bar_id [-s sock_path] [-t] [-h] [-v]\n", elf_name);
printf("\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("-t, --transparency Enable transparency (RGBA colors)\n");
printf("-h, --help Display this help message and exit\n");
printf("-v, --version Display version number and exit\n");
printf("-V, --verbose Enable verbose mode\n");
@ -105,12 +106,13 @@ int main(int argc, char **argv) {
static struct option long_opt[] = {
{"socket", required_argument, 0, 's'},
{"bar_id", required_argument, 0, 'b'},
{"transparency", no_argument, 0, 't'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"verbose", no_argument, 0, 'V'},
{NULL, 0, 0, 0}};
while ((opt = getopt_long(argc, argv, "b:s:hvV", long_opt, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) {
switch (opt) {
case 's':
socket_path = expand_path(optarg);
@ -122,6 +124,9 @@ int main(int argc, char **argv) {
case 'b':
config.bar_id = sstrdup(optarg);
break;
case 't':
config.transparency = true;
break;
case 'V':
config.verbose = true;
break;

View File

@ -213,7 +213,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
render->width = predict_text_width(text);
if (block->border)
render->width += logical_px(2);
render->width += logical_px(block->border_left + block->border_right);
/* Compute offset and append for text aligment in min_width. */
if (block->min_width <= render->width) {
@ -287,8 +287,8 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
color_t bg_color = bar_color;
int border_width = (block->border) ? logical_px(1) : 0;
int full_render_width = render->width + render->x_offset + render->x_append;
int has_border = block->border ? 1 : 0;
if (block->border || block->background || block->urgent) {
/* Let's determine the colors first. */
color_t border_color = bar_color;
@ -310,15 +310,16 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
/* Draw the background. */
draw_util_rectangle(&output->statusline_buffer, bg_color,
x + border_width,
logical_px(1) + border_width,
full_render_width - 2 * border_width,
bar_height - 2 * border_width - logical_px(2));
x + has_border * logical_px(block->border_left),
logical_px(1) + has_border * logical_px(block->border_top),
full_render_width - has_border * logical_px(block->border_right + block->border_left),
bar_height - has_border * logical_px(block->border_bottom + block->border_top) - logical_px(2));
}
draw_util_text(text, &output->statusline_buffer, fg_color, bg_color,
x + render->x_offset + border_width, logical_px(ws_voff_px),
render->width - 2 * border_width);
x + render->x_offset + has_border * logical_px(block->border_left),
bar_height / 2 - font.height / 2,
render->width - has_border * logical_px(block->border_left + block->border_right));
x += full_render_width;
/* If this is not the last block, draw a separator. */
@ -454,6 +455,50 @@ static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_relea
return false;
}
static void child_handle_button(xcb_button_press_event_t *event, i3_output *output, uint32_t statusline_x) {
if (statusline_x > (uint32_t)output->statusline_width) {
return;
}
/* x of the start of the current block relative to the statusline. */
uint32_t last_block_x = 0;
struct status_block *block;
TAILQ_FOREACH(block, &statusline_head, blocks) {
i3String *text;
struct status_block_render_desc *render;
if (output->statusline_short_text && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
} else {
text = block->full_text;
render = &block->full_render;
}
if (i3string_get_num_bytes(text) == 0) {
continue;
}
/* Include the whole block in our calculations: when min_width is
* specified, we have to take padding width into account. */
const uint32_t full_render_width = render->width + render->x_offset + render->x_append;
/* x of the click event relative to the current block. */
const uint32_t relative_x = statusline_x - last_block_x;
if (relative_x <= full_render_width) {
send_block_clicked(event->detail, block->name, block->instance,
event->root_x, event->root_y, relative_x,
event->event_y, full_render_width, bar_height,
event->state);
return;
}
last_block_x += full_render_width + block->sep_block_width;
if (last_block_x > statusline_x) {
/* Click was on a separator. */
return;
}
}
}
/*
* Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll-
@ -479,10 +524,6 @@ static void handle_button(xcb_button_press_event_t *event) {
/* During button release events, only check for custom commands. */
const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
if (event_is_release) {
execute_custom_command(event->detail, event_is_release);
return;
}
int32_t x = event->event_x >= 0 ? event->event_x : 0;
int workspace_width = 0;
@ -499,44 +540,32 @@ static void handle_button(xcb_button_press_event_t *event) {
workspace_width += logical_px(ws_spacing_px);
}
if (x > workspace_width && child_want_click_events()) {
/* If the child asked for click events,
* check if a status block has been clicked. */
int tray_width = get_tray_width(walk->trayclients);
int last_block_x = 0;
int offset = walk->rect.w - walk->statusline_width - tray_width - logical_px((tray_width > 0) * sb_hoff_px);
int32_t statusline_x = x - offset;
if (child_want_click_events() && x > workspace_width) {
const int tray_width = get_tray_width(walk->trayclients);
/* Calculate the horizontal coordinate (x) of the start of the
* statusline by subtracting its width and the width of the tray from
* the bar width. */
const int offset = walk->rect.w - walk->statusline_width -
tray_width - logical_px((tray_width > 0) * sb_hoff_px);
if (x >= offset) {
/* Click was after the start of the statusline, return to avoid
* executing any other actions even if a click event is not
* produced eventually. */
if (statusline_x >= 0 && statusline_x < walk->statusline_width) {
struct status_block *block;
TAILQ_FOREACH(block, &statusline_head, blocks) {
i3String *text = block->full_text;
struct status_block_render_desc *render = &block->full_render;
if (walk->statusline_short_text && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
if (!event_is_release) {
/* x of the click event relative to the start of the
* statusline. */
const uint32_t statusline_x = x - offset;
child_handle_button(event, walk, statusline_x);
}
if (i3string_get_num_bytes(text) == 0)
continue;
const int relative_x = statusline_x - last_block_x;
if (relative_x >= 0 && (uint32_t)relative_x <= render->width) {
send_block_clicked(event->detail, block->name, block->instance,
event->root_x, event->root_y, relative_x, event->event_y, render->width, bar_height,
event->state);
return;
}
last_block_x += render->width + render->x_append + render->x_offset + block->sep_block_width;
}
}
}
/* If a custom command was specified for this mouse button, it overrides
* the default behavior. */
if (execute_custom_command(event->detail, event_is_release)) {
if (execute_custom_command(event->detail, event_is_release) || event_is_release) {
return;
}
@ -1169,7 +1198,21 @@ char *init_xcb_early(void) {
depth = root_screen->root_depth;
colormap = root_screen->default_colormap;
visual_type = config.transparency ? xcb_aux_find_visual_by_attrs(root_screen, -1, 32) : NULL;
if (visual_type != NULL) {
depth = xcb_aux_get_depth_of_visual(root_screen, visual_type->visual_id);
colormap = xcb_generate_id(xcb_connection);
xcb_void_cookie_t cm_cookie = xcb_create_colormap_checked(xcb_connection,
XCB_COLORMAP_ALLOC_NONE,
colormap,
xcb_root,
visual_type->visual_id);
if (xcb_request_failed(cm_cookie, "Could not allocate colormap")) {
exit(EXIT_FAILURE);
}
} else {
visual_type = get_visualtype(root_screen);
}
xcb_cursor_context_t *cursor_ctx;
if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) == 0) {

View File

@ -22,6 +22,10 @@ struct CommandResultIR {
/* The JSON generator to append a reply to (may be NULL). */
yajl_gen json_gen;
/* The IPC client connection which sent this command (may be NULL, e.g. for
key bindings). */
ipc_client *client;
/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
@ -61,7 +65,7 @@ char *parse_string(const char **walk, bool as_word);
*
* Free the returned CommandResult with command_result_free().
*/
CommandResult *parse_command(const char *input, yajl_gen gen);
CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client);
/**
* Frees a CommandResult

View File

@ -378,13 +378,6 @@ orientation_t con_orientation(Con *con);
*/
Con *con_next_focused(Con *con);
/**
* Get the next/previous container in the specified orientation. This may
* travel up until it finds a container with suitable orientation.
*
*/
Con *con_get_next(Con *con, char way, orientation_t orientation);
/**
* Returns the focused con inside this client, descending the tree as far as
* possible. This comes in handy when attaching a con to a workspace at the
@ -533,3 +526,10 @@ i3String *con_parse_title_format(Con *con);
*
*/
bool con_swap(Con *first, Con *second);
/**
* Returns given container's rect size depending on its orientation.
* i.e. its width when horizontal, its height when vertical.
*
*/
uint32_t con_rect_size_in_orientation(Con *con);

View File

@ -400,28 +400,24 @@ struct tray_output_t {
tray_outputs;
};
/**
* Finds the configuration file to use (either the one specified by
* override_configpath), the users one or the system default) and calls
* parse_file().
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*
* If use_nagbar is false, don't try to start i3-nagbar but log the errors to
* stdout/stderr instead.
*
*/
bool parse_configuration(const char *override_configpath, bool use_nagbar);
typedef enum {
C_VALIDATE,
C_LOAD,
C_RELOAD,
} config_load_t;
/**
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
* (Re-)loads the configuration file (sets useful defaults before).
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*
* load_type specifies the type of loading: C_VALIDATE is used to only verify
* the correctness of the config file (used with the flag -C). C_LOAD will load
* the config for normal use and display errors in the nagbar. C_RELOAD will
* also clear the previous config.
*/
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
bool load_configuration(const char *override_configfile, config_load_t load_type);
/**
* Ungrabs all keys, to be called before re-grabbing the keys because of a
@ -435,14 +431,3 @@ void ungrab_all_keys(xcb_connection_t *conn);
*
*/
void update_barconfig(void);
/**
* Kills the configerror i3-nagbar process, if any.
*
* Called when reloading/restarting.
*
* If wait_for_it is set (restarting), this function will waitpid(), otherwise,
* ev is assumed to handle it (reloading).
*
*/
void kill_configerror_nagbar(bool wait_for_it);

View File

@ -482,7 +482,13 @@ struct Window {
int max_height;
/* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
double aspect_ratio;
double min_aspect_ratio;
double max_aspect_ratio;
/** The window has a nonrectangular shape. */
bool shaped;
/** The window has a nonrectangular input shape. */
bool input_shaped;
};
/**

View File

@ -11,6 +11,12 @@
#include <config.h>
/**
* Updates all the EWMH desktop properties.
*
*/
void ewmh_update_desktop_properties(void);
/**
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
@ -20,24 +26,6 @@
*/
void ewmh_update_current_desktop(void);
/**
* Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of
* noninternal workspaces.
*/
void ewmh_update_number_of_desktops(void);
/**
* Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
* list of NULL-terminated strings in UTF-8 encoding"
*/
void ewmh_update_desktop_names(void);
/**
* Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
* define the top left corner of each desktop's viewport.
*/
void ewmh_update_desktop_viewport(void);
/**
* Updates _NET_WM_DESKTOP for all windows.
* A request will only be made if the cached value differs from the calculated value.

View File

@ -94,12 +94,17 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event);
/**
* Called when a floating window is created or resized.
* This function resizes the window if its size is higher or lower than the
* configured maximum/minimum size, respectively.
* Called when a floating window is created or resized. This function resizes
* the window if its size is higher or lower than the configured maximum/minimum
* size, respectively or when adjustments are needed to conform to the
* configured size increments or aspect ratio limits.
*
* When prefer_height is true and the window needs to be resized because of the
* configured aspect ratio, the width is adjusted first, preserving the previous
* height.
*
*/
void floating_check_size(Con *floating_con);
void floating_check_size(Con *floating_con, bool prefer_height);
/**
* This is the return value of a drag operation like drag_pointer.
@ -152,7 +157,7 @@ bool floating_reposition(Con *con, Rect newrect);
* window's size hints.
*
*/
void floating_resize(Con *floating_con, int x, int y);
void floating_resize(Con *floating_con, uint32_t x, uint32_t y);
/**
* Fixes the coordinates of the floating window whenever the window gets

View File

@ -16,6 +16,7 @@
extern int randr_base;
extern int xkb_base;
extern int shape_base;
/**
* Adds the given sequence to the list of events which are ignored.

View File

@ -14,6 +14,7 @@
#include <sys/time.h>
#include <sys/resource.h>
#include <xcb/shape.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xkb.h>
@ -70,7 +71,7 @@ extern uint8_t root_depth;
extern xcb_visualid_t visual_id;
extern xcb_colormap_t colormap;
extern bool xcursor_supported, xkb_supported;
extern bool xcursor_supported, xkb_supported, shape_supported;
extern xcb_window_t root;
extern struct ev_loop *main_loop;
extern bool only_check_config;

View File

@ -72,6 +72,16 @@ typedef void (*handler_t)(ipc_client *, uint8_t *, int, uint32_t, uint32_t);
*/
void ipc_new_client(EV_P_ struct ev_io *w, int revents);
/**
* ipc_new_client_on_fd() only sets up the event handler
* for activity on the new connection and inserts the file descriptor into
* the list of clients.
*
* This variant is useful for the inherited IPC connection when restarting.
*
*/
ipc_client *ipc_new_client_on_fd(EV_P_ int fd);
/**
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
@ -95,10 +105,13 @@ typedef enum {
} shutdown_reason_t;
/**
* Calls shutdown() on each socket and closes it.
* Calls shutdown() on each socket and closes it. This function is to be called
* when exiting or restarting only!
*
* exempt_fd is never closed. Set to -1 to close all fds.
*
*/
void ipc_shutdown(shutdown_reason_t reason);
void ipc_shutdown(shutdown_reason_t reason, int exempt_fd);
void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
@ -136,3 +149,8 @@ void ipc_send_binding_event(const char *event_type, Binding *bind);
* socket.
*/
void ipc_set_kill_timeout(ev_tstamp new);
/**
* Sends a restart reply to the IPC client on the specified fd.
*/
void ipc_confirm_restart(ipc_client *client);

View File

@ -48,7 +48,7 @@ void output_init_con(Output *output);
* Create the first unused workspace.
*
*/
void init_ws_for_output(Output *output, Con *content);
void init_ws_for_output(Output *output);
/**
* Initializes the specified output, assigning the specified workspace to it.

View File

@ -40,7 +40,7 @@ typedef struct render_params {
* updated in X11.
*
*/
void render_con(Con *con, bool render_fullscreen);
void render_con(Con *con);
/**
* Returns the height for the decorations

View File

@ -31,9 +31,3 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt);
*
*/
double percent_for_1px(Con *con);
/**
* Calculate the given container's new percent given a change in pixels.
*
*/
double px_resize_to_percent(Con *con, int px_diff);

View File

@ -73,10 +73,6 @@ void tree_next(char way, orientation_t orientation);
* The dont_kill_parent flag is specified when the function calls itself
* recursively while deleting a containers children.
*
* The force_set_focus flag is specified in the case of killing a floating
* window: tree_close_internal() will be invoked for the CT_FLOATINGCON (the parent
* container) and focus should be set there.
*
*/
bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent);

View File

@ -70,6 +70,12 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
*/
void window_update_type(i3Window *window, xcb_get_property_reply_t *reply);
/**
* Updates the WM_NORMAL_HINTS
*
*/
bool window_update_normal_hints(i3Window *win, xcb_get_property_reply_t *reply, xcb_get_geometry_reply_t *geom);
/**
* Updates the WM_HINTS (we only care about the input focus handling part).
*

View File

@ -24,6 +24,13 @@
#define NET_WM_DESKTOP_NONE 0xFFFFFFF0
#define NET_WM_DESKTOP_ALL 0xFFFFFFFF
/**
* Stores a copy of the name of the last used workspace for the workspace
* back-and-forth switching.
*
*/
extern char *previous_workspace_name;
/**
* Returns the workspace with the given name or NULL if such a workspace does
* not exist.
@ -211,7 +218,6 @@ 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, Output *output);
void workspace_move_to_output(Con *ws, Output *output);

View File

@ -137,3 +137,8 @@ void x_set_warp_to(Rect *rect);
*
*/
void x_mask_event_mask(uint32_t mask);
/**
* Enables or disables nonrectangular shape of the container frame.
*/
void x_set_shape(Con *con, xcb_shape_sk_t kind, bool enable);

View File

@ -163,6 +163,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
i3Font font;
font.type = FONT_TYPE_NONE;
font.pattern = NULL;
/* No XCB connction, return early because we're just validating the
* configuration file. */
@ -435,6 +436,7 @@ static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) {
* a crash. Plus, the user will see the error in their log. */
fprintf(stderr, "Could not get text extents (X error code %d)\n",
error->error_code);
free(error);
return savedFont->specific.xcb.info->max_bounds.character_width * text_len;
}

View File

@ -69,32 +69,41 @@ xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
xcb_char2b_t *buffer = smalloc(buffer_size);
/* We need to use an additional pointer, because iconv() modifies it */
size_t output_size = buffer_size;
size_t output_bytes_left = buffer_size;
xcb_char2b_t *output = buffer;
if (ucs2_conversion_descriptor == (iconv_t)-1) {
/* Get a new conversion descriptor */
/* Get a new conversion descriptor. //IGNORE is a GNU suffix that makes
* iconv to silently discard characters that cannot be represented in
* the target character set. */
ucs2_conversion_descriptor = iconv_open("UCS-2BE//IGNORE", "UTF-8");
if (ucs2_conversion_descriptor == (iconv_t)-1) {
ucs2_conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
if (ucs2_conversion_descriptor == (iconv_t)-1)
}
if (ucs2_conversion_descriptor == (iconv_t)-1) {
err(EXIT_FAILURE, "Error opening the conversion context");
}
} else {
/* Reset the existing conversion descriptor */
iconv(ucs2_conversion_descriptor, NULL, NULL, NULL, NULL);
}
/* Do the conversion */
size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size, (char **)&output, &output_size);
size_t rc = iconv(ucs2_conversion_descriptor, &input, &input_size,
(char **)&output, &output_bytes_left);
if (rc == (size_t)-1) {
/* Conversion will only be partial. */
perror("Converting to UCS-2 failed");
free(buffer);
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
}
/* If no bytes where converted, this is equivalent to freeing buffer. */
buffer_size -= output_bytes_left;
buffer = srealloc(buffer, buffer_size);
/* Return the resulting string's length */
if (real_strlen != NULL)
*real_strlen = (buffer_size - output_size) / sizeof(xcb_char2b_t);
if (real_strlen != NULL) {
*real_strlen = buffer_size / sizeof(xcb_char2b_t);
}
return buffer;
}

View File

@ -90,6 +90,15 @@ See the -m option for continuous monitoring.
i3-msg is a sample implementation for a client using the unix socket IPC
interface to i3.
=== Exit status:
0:
if OK,
1:
if invalid syntax or unable to connect to ipc-socket
2:
if i3 returned an error processing your command(s)
== EXAMPLES
------------------------------------------------

View File

@ -166,7 +166,7 @@ Exits i3.
== FILES
=== \~/.i3/config (or ~/.config/i3/config)
=== \~/.config/i3/config (or ~/.i3/config)
When starting, i3 looks for configuration files in the following order:

View File

@ -277,11 +277,13 @@ state WORKSPACE:
state WORKSPACE_OUTPUT:
'output'
-> WORKSPACE_OUTPUT_STR
-> WORKSPACE_OUTPUT_WORD
state WORKSPACE_OUTPUT_STR:
output = string
-> call cfg_workspace($workspace, $output)
state WORKSPACE_OUTPUT_WORD:
output = word
-> call cfg_workspace($workspace, $output); WORKSPACE_OUTPUT_WORD
end
-> INITIAL
# ipc-socket <path>
state IPC_SOCKET:

View File

@ -1,8 +1,8 @@
#!/bin/zsh
# This script is used to prepare a new release of i3.
export RELEASE_VERSION="4.15"
export PREVIOUS_VERSION="4.14"
export RELEASE_VERSION="4.16"
export PREVIOUS_VERSION="4.15"
export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
@ -155,6 +155,12 @@ git checkout ${RELEASE_BRANCH}
cd ${TMPDIR}
git clone --quiet ${STARTDIR}/../i3.github.io
cd i3.github.io
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}"
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
@ -166,11 +172,6 @@ sed -i "s,<tbody>,<tbody>\n <tr>\n <td>${RELEASE_VERSION}</td>\n <td><a h
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)"

View File

@ -48,7 +48,7 @@ void run_assignments(i3Window *window) {
DLOG("matching assignment, execute command %s\n", current->dest.command);
char *full_command;
sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
CommandResult *result = parse_command(full_command, NULL);
CommandResult *result = parse_command(full_command, NULL, NULL);
free(full_command);
if (result->needs_tree_render)

View File

@ -123,7 +123,7 @@ static bool binding_in_current_group(const Binding *bind) {
}
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
/* Grab the key in all combinations */
/* Grab the key in all combinations */
#define GRAB_KEY(modifier) \
do { \
xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
@ -621,7 +621,7 @@ void switch_mode(const char *new_mode) {
DLOG("Switching to mode %s\n", new_mode);
SLIST_FOREACH(mode, &modes, modes) {
if (strcasecmp(mode->name, new_mode) != 0)
if (strcmp(mode->name, new_mode) != 0)
continue;
ungrab_all_keys(conn);
@ -824,7 +824,7 @@ CommandResult *run_binding(Binding *bind, Con *con) {
sasprintf(&command, "[con_id=\"%p\"] %s", con, bind->command);
Binding *bind_cp = binding_copy(bind);
CommandResult *result = parse_command(command, NULL);
CommandResult *result = parse_command(command, NULL, NULL);
free(command);
if (result->needs_tree_render)

View File

@ -302,12 +302,6 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
goto done;
}
if (in_stacked) {
/* for stacked/tabbed cons, the resizing applies to the parent
* container */
con = con->parent;
}
/* 7: floating modifier pressed, initiate a resize */
if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
if (floating_mod_on_tiled_client(con, event))

View File

@ -12,6 +12,8 @@
#include <stdint.h>
#include <float.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include "shmlog.h"
@ -463,7 +465,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
} else {
floating_con->rect.width += px;
}
floating_check_size(floating_con);
floating_check_size(floating_con, orientation == VERT);
/* Did we actually resize anything or did the size constraints prevent us?
* If we could not resize, exit now to not move the window. */
@ -533,8 +535,9 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir
if (ppt != 0.0) {
new_current_percent = current->percent + ppt;
} else {
new_current_percent = px_resize_to_percent(current, px);
ppt = new_current_percent - current->percent;
/* Convert px change to change in percentages */
ppt = (double)px / (double)con_rect_size_in_orientation(current->parent);
new_current_percent = current->percent + ppt;
}
subtract_percent = ppt / (children - 1);
if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) {
@ -600,16 +603,20 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
const double ppt = (double)resize_ppt / 100.0;
if (!cmd_resize_tiling_width_height(current_match, cmd_output,
current->con, direction,
resize_px, ppt))
resize_px, ppt)) {
yerror("Cannot resize.");
return;
}
} else {
if (!cmd_resize_tiling_direction(current_match, cmd_output,
current->con, direction,
resize_px, resize_ppt))
resize_px, resize_ppt)) {
yerror("Cannot resize.");
return;
}
}
}
}
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
@ -652,7 +659,7 @@ static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientat
void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
if (cwidth < 0 || cheight < 0) {
ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
yerror("Dimensions cannot be negative.");
return;
}
@ -777,6 +784,7 @@ void cmd_append_layout(I3_CMD, const char *cpath) {
char *buf = NULL;
ssize_t len;
if ((len = slurp(path, &buf)) < 0) {
yerror("Could not slurp \"%s\".", path);
/* slurp already logged an error. */
goto out;
}
@ -826,7 +834,7 @@ void cmd_append_layout(I3_CMD, const char *cpath) {
// is not executed yet and will be batched with append_layouts
// needs_tree_render after the parser finished. We should check if that is
// necessary at all.
render_con(croot, false);
render_con(croot);
restore_open_placeholder_windows(parent);
@ -1108,22 +1116,13 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) {
}
Output *current_output = get_output_for_con(ws);
if (current_output == NULL) {
yerror("Cannot get current output. This is a bug in i3.");
return;
}
Output *target_output = get_output_from_string(current_output, name);
if (!target_output) {
yerror("Could not get output from string \"%s\"", name);
return;
}
bool success = workspace_move_to_output(ws, target_output);
if (!success) {
yerror("Failed to move workspace to output.");
return;
}
workspace_move_to_output(ws, target_output);
}
cmd_output->needs_tree_render = true;
@ -1508,7 +1507,7 @@ void cmd_layout(I3_CMD, const char *layout_str) {
layout_t layout;
if (!layout_from_name(layout_str, &layout)) {
ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
yerror("Unknown layout \"%s\", this is a mismatch between code and parser spec.", layout_str);
return;
}
@ -1576,7 +1575,7 @@ void cmd_reload(I3_CMD) {
LOG("reloading\n");
kill_nagbar(&config_error_nagbar_pid, false);
kill_nagbar(&command_error_nagbar_pid, false);
load_configuration(conn, NULL, true);
load_configuration(NULL, C_RELOAD);
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */
ipc_send_workspace_event("reload", NULL, NULL);
@ -1593,15 +1592,27 @@ void cmd_reload(I3_CMD) {
*/
void cmd_restart(I3_CMD) {
LOG("restarting i3\n");
ipc_shutdown(SHUTDOWN_REASON_RESTART);
int exempt_fd = -1;
if (cmd_output->client != NULL) {
exempt_fd = cmd_output->client->fd;
LOG("Carrying file descriptor %d across restart\n", exempt_fd);
int flags;
if ((flags = fcntl(exempt_fd, F_GETFD)) < 0 ||
fcntl(exempt_fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) {
ELOG("Could not disable FD_CLOEXEC on fd %d\n", exempt_fd);
}
char *fdstr = NULL;
sasprintf(&fdstr, "%d", exempt_fd);
setenv("_I3_RESTART_FD", fdstr, 1);
}
ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd);
unlink(config.ipc_socket_path);
/* We need to call this manually since atexit handlers dont get called
* when exec()ing */
purge_zerobyte_logfile();
i3_restart(false);
// XXX: default reply for now, make this a better reply
ysuccess(true);
/* unreached */
assert(false);
}
/*
@ -1629,24 +1640,18 @@ void cmd_open(I3_CMD) {
*
*/
void cmd_focus_output(I3_CMD, const char *name) {
owindow *current;
DLOG("name = %s\n", name);
HANDLE_EMPTY_MATCH;
/* get the output */
Output *current_output = NULL;
Output *output;
if (TAILQ_EMPTY(&owindows)) {
ysuccess(true);
return;
}
TAILQ_FOREACH(current, &owindows, owindows)
current_output = get_output_for_con(current->con);
assert(current_output != NULL);
output = get_output_from_string(current_output, name);
Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con);
Output *output = get_output_from_string(current_output, name);
if (!output) {
yerror("No such output found.");
yerror("Output %s not found.", name);
return;
}
@ -1661,7 +1666,6 @@ void cmd_focus_output(I3_CMD, const char *name) {
workspace_show(ws);
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
@ -2012,9 +2016,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
cmd_output->needs_tree_render = true;
ysuccess(true);
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_current_desktop();
ewmh_update_desktop_properties();
startup_sequence_rename_workspace(old_name_copy, new_name);
free(old_name_copy);

View File

@ -181,6 +181,7 @@ static struct CommandResultIR command_output;
static void next_state(const cmdp_token *token) {
if (token->next_state == __CALL) {
subcommand_output.json_gen = command_output.json_gen;
subcommand_output.client = command_output.client;
subcommand_output.needs_tree_render = false;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
state = subcommand_output.next_state;
@ -261,11 +262,13 @@ char *parse_string(const char **walk, bool as_word) {
*
* Free the returned CommandResult with command_result_free().
*/
CommandResult *parse_command(const char *input, yajl_gen gen) {
CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client) {
DLOG("COMMAND: *%s*\n", input);
state = INITIAL;
CommandResult *result = scalloc(1, sizeof(CommandResult));
command_output.client = client;
/* A YAJL JSON generator used for formatting replies. */
command_output.json_gen = gen;
@ -353,7 +356,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen) {
if (*walk == '\0' || *walk == ',' || *walk == ';') {
next_state(token);
token_handled = true;
/* To make sure we start with an appropriate matching
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */
@ -499,7 +502,7 @@ int main(int argc, char *argv[]) {
}
yajl_gen gen = yajl_gen_alloc(NULL);
CommandResult *result = parse_command(argv[1], gen);
CommandResult *result = parse_command(argv[1], gen, NULL);
command_result_free(result);

245
src/con.c
View File

@ -46,7 +46,6 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->current_border_width = -1;
if (window) {
new->depth = window->depth;
new->window->aspect_ratio = 0.0;
} else {
new->depth = root_depth;
}
@ -540,7 +539,6 @@ bool con_is_internal(Con *con) {
*/
bool con_is_floating(Con *con) {
assert(con != NULL);
DLOG("checking if con %p is floating\n", con);
return (con->floating >= FLOATING_AUTO_ON);
}
@ -1401,8 +1399,6 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates) {
*/
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates) {
Output *current_output = get_output_for_con(con);
assert(current_output != NULL);
Output *output = get_output_from_string(current_output, name);
if (output == NULL) {
ELOG("Could not find output \"%s\"\n", name);
@ -1486,42 +1482,6 @@ Con *con_next_focused(Con *con) {
return next;
}
/*
* Get the next/previous container in the specified orientation. This may
* travel up until it finds a container with suitable orientation.
*
*/
Con *con_get_next(Con *con, char way, orientation_t orientation) {
DLOG("con_get_next(way=%c, orientation=%d)\n", way, orientation);
/* 1: get the first parent with the same orientation */
Con *cur = con;
while (con_orientation(cur->parent) != orientation) {
DLOG("need to go one level further up\n");
if (cur->parent->type == CT_WORKSPACE) {
LOG("that's a workspace, we can't go further up\n");
return NULL;
}
cur = cur->parent;
}
/* 2: chose next (or previous) */
Con *next;
if (way == 'n') {
next = TAILQ_NEXT(cur, nodes);
/* if we are at the end of the list, we need to wrap */
if (next == TAILQ_END(&(parent->nodes_head)))
return NULL;
} else {
next = TAILQ_PREV(cur, nodes_head, nodes);
/* if we are at the end of the list, we need to wrap */
if (next == TAILQ_END(&(cur->nodes_head)))
return NULL;
}
DLOG("next = %p\n", next);
return next;
}
/*
* Returns the focused con inside this client, descending the tree as far as
* possible. This comes in handy when attaching a con to a workspace at the
@ -2333,11 +2293,6 @@ bool con_swap(Con *first, Con *second) {
return false;
}
if (con_is_floating(first) || con_is_floating(second)) {
ELOG("Floating windows cannot be swapped.\n");
return false;
}
if (first == second) {
DLOG("Swapping container %p with itself, nothing to do.\n", first);
return false;
@ -2348,132 +2303,80 @@ bool con_swap(Con *first, Con *second) {
return false;
}
Con *old_focus = focused;
Con *first_ws = con_get_workspace(first);
Con *second_ws = con_get_workspace(second);
Con *current_ws = con_get_workspace(old_focus);
const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first));
const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second));
fullscreen_mode_t first_fullscreen_mode = first->fullscreen_mode;
fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode;
if (first_fullscreen_mode != CF_NONE) {
con_disable_fullscreen(first);
}
if (second_fullscreen_mode != CF_NONE) {
con_disable_fullscreen(second);
Con *ws1 = con_get_workspace(first);
Con *ws2 = con_get_workspace(second);
Con *restore_focus = NULL;
if (ws1 == ws2 && ws1 == con_get_workspace(focused)) {
/* Preserve focus in the current workspace. */
restore_focus = focused;
} else if (first == focused || con_has_parent(focused, first)) {
restore_focus = second;
} else if (second == focused || con_has_parent(focused, second)) {
restore_focus = first;
}
double first_percent = first->percent;
double second_percent = second->percent;
#define SWAP_CONS_IN_TREE(headname, field) \
do { \
struct headname *head1 = &(first->parent->headname); \
struct headname *head2 = &(second->parent->headname); \
Con *first_prev = TAILQ_PREV(first, headname, field); \
Con *second_prev = TAILQ_PREV(second, headname, field); \
if (second_prev == first) { \
TAILQ_SWAP(first, second, head1, field); \
} else if (first_prev == second) { \
TAILQ_SWAP(second, first, head1, field); \
} else { \
TAILQ_REMOVE(head1, first, field); \
TAILQ_REMOVE(head2, second, field); \
if (second_prev == NULL) { \
TAILQ_INSERT_HEAD(head2, first, field); \
} else { \
TAILQ_INSERT_AFTER(head2, second_prev, first, field); \
} \
if (first_prev == NULL) { \
TAILQ_INSERT_HEAD(head1, second, field); \
} else { \
TAILQ_INSERT_AFTER(head1, first_prev, second, field); \
} \
} \
} while (0)
/* De- and reattaching the containers will insert them at the tail of the
* focus_heads. We will need to fix this. But we need to make sure first
* and second don't get in each other's way if they share the same parent,
* so we select the closest previous focus_head that isn't involved. */
Con *first_prev_focus_head = first;
while (first_prev_focus_head == first || first_prev_focus_head == second) {
first_prev_focus_head = TAILQ_PREV(first_prev_focus_head, focus_head, focused);
}
SWAP_CONS_IN_TREE(nodes_head, nodes);
SWAP_CONS_IN_TREE(focus_head, focused);
SWAP(first->parent, second->parent, Con *);
Con *second_prev_focus_head = second;
while (second_prev_focus_head == second || second_prev_focus_head == first) {
second_prev_focus_head = TAILQ_PREV(second_prev_focus_head, focus_head, focused);
}
/* We use a fake container to mark the spot of where the second container needs to go. */
Con *fake = con_new(NULL, NULL);
fake->layout = L_SPLITH;
_con_attach(fake, first->parent, first, true);
bool result = true;
/* Swap the containers. We set the ignore_focus flag here because after the
* container is attached, the focus order is not yet correct and would
* result in wrong windows being focused. */
/* Move first to second. */
result &= _con_move_to_con(first, second, false, false, false, true, false);
/* If swapping the containers didn't work we don't need to mess with the focus. */
if (!result) {
goto swap_end;
}
/* If we moved the container holding the focused window to another
* workspace we need to ensure the visible workspace has the focused
* container.
* We don't need to check this for the second container because we've only
* moved the first one at this point.*/
if (first_ws != second_ws && focused_within_first) {
con_activate(con_descend_focused(current_ws));
}
/* Move second to where first has been originally. */
result &= _con_move_to_con(second, fake, false, false, false, true, false);
if (!result) {
goto swap_end;
}
/* Swapping will have inserted the containers at the tail of their parents'
* focus head. We fix this now by putting them in the position of the focus
* head the container they swapped with was in. */
TAILQ_REMOVE(&(first->parent->focus_head), first, focused);
TAILQ_REMOVE(&(second->parent->focus_head), second, focused);
if (second_prev_focus_head == NULL) {
TAILQ_INSERT_HEAD(&(first->parent->focus_head), first, focused);
} else {
TAILQ_INSERT_AFTER(&(first->parent->focus_head), second_prev_focus_head, first, focused);
}
if (first_prev_focus_head == NULL) {
TAILQ_INSERT_HEAD(&(second->parent->focus_head), second, focused);
} else {
TAILQ_INSERT_AFTER(&(second->parent->focus_head), first_prev_focus_head, second, focused);
}
/* If the focus was within any of the swapped containers, do the following:
* - If swapping took place within a workspace, ensure the previously
* focused container stays focused.
* - Otherwise, focus the container that has been swapped in.
*
* To understand why fixing the focus_head previously wasn't enough,
* consider the scenario
* H[ V[ A X ] V[ Y B ] ]
* with B being focused, but X being the focus_head within its parent. If
* we swap A and B now, fixing the focus_head would focus X, but since B
* was the focused container before it should stay focused.
*/
if (focused_within_first) {
if (first_ws == second_ws) {
con_activate(old_focus);
} else {
con_activate(con_descend_focused(second));
}
} else if (focused_within_second) {
if (first_ws == second_ws) {
con_activate(old_focus);
} else {
con_activate(con_descend_focused(first));
}
}
/* Floating nodes are children of CT_FLOATING_CONs, they are listed in
* nodes_head and focus_head like all other containers. Thus, we don't need
* to do anything special other than swapping the floating status and the
* relevant rects. */
SWAP(first->floating, second->floating, int);
SWAP(first->rect, second->rect, Rect);
SWAP(first->window_rect, second->window_rect, Rect);
/* We need to copy each other's percentages to ensure that the geometry
* doesn't change during the swap. This needs to happen _before_ we close
* the fake container as closing the tree will recalculate percentages. */
first->percent = second_percent;
second->percent = first_percent;
fake->percent = 0.0;
* doesn't change during the swap. */
SWAP(first->percent, second->percent, double);
SWAP(first_fullscreen_mode, second_fullscreen_mode, fullscreen_mode_t);
swap_end:
/* The two windows exchange their original fullscreen status */
if (first_fullscreen_mode != CF_NONE) {
con_enable_fullscreen(first, first_fullscreen_mode);
if (restore_focus) {
con_focus(restore_focus);
}
if (second_fullscreen_mode != CF_NONE) {
con_enable_fullscreen(second, second_fullscreen_mode);
/* Update new parents' & workspaces' urgency. */
con_set_urgency(first, first->urgent);
con_set_urgency(second, second->urgent);
/* Exchange fullscreen modes, can't use SWAP because we need to call the
* correct functions. */
fullscreen_mode_t second_fullscreen_mode = second->fullscreen_mode;
if (first->fullscreen_mode == CF_NONE) {
con_disable_fullscreen(second);
} else {
con_enable_fullscreen(second, first->fullscreen_mode);
}
if (second_fullscreen_mode == CF_NONE) {
con_disable_fullscreen(first);
} else {
con_enable_fullscreen(first, second_fullscreen_mode);
}
/* We don't actually need this since percentages-wise we haven't changed
@ -2482,11 +2385,19 @@ swap_end:
con_fix_percent(first->parent);
con_fix_percent(second->parent);
/* We can get rid of the fake container again now. */
con_close(fake, DONT_KILL_WINDOW);
FREE(first->deco_render_params);
FREE(second->deco_render_params);
con_force_split_parents_redraw(first);
con_force_split_parents_redraw(second);
return result;
return true;
}
/*
* Returns container's rect size depending on its orientation.
* i.e. its width when horizontal, its height when vertical.
*
*/
uint32_t con_rect_size_in_orientation(Con *con) {
return (con_orientation(con) == HORIZ ? con->rect.width : con->rect.height);
}

View File

@ -39,42 +39,12 @@ void update_barconfig(void) {
}
}
/*
* Finds the configuration file to use (either the one specified by
* override_configpath), the users one or the system default) and calls
* parse_file().
*
*/
bool parse_configuration(const char *override_configpath, bool use_nagbar) {
char *path = get_config_path(override_configpath, true);
if (path == NULL) {
die("Unable to find the configuration file (looked at "
"$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
"and " SYSCONFDIR "/i3/config)");
}
static void free_configuration(void) {
assert(conn != NULL);
LOG("Parsing configfile %s\n", path);
FREE(current_configpath);
current_configpath = path;
/* initialize default bindings if we're just validating the config file */
if (!use_nagbar && bindings == NULL) {
bindings = scalloc(1, sizeof(struct bindings_head));
TAILQ_INIT(bindings);
}
return parse_file(path, use_nagbar);
}
/*
* (Re-)loads the configuration file (sets useful defaults before).
*
*/
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
if (reload) {
/* If we are currently in a binding mode, we first revert to the
* default since we have no guarantee that the current mode will even
* still exist after parsing the config again. See #2228. */
/* If we are currently in a binding mode, we first revert to the default
* since we have no guarantee that the current mode will even still exist
* after parsing the config again. See #2228. */
switch_mode("default");
/* First ungrab the keys */
@ -187,6 +157,23 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
free(config.ipc_socket_path);
free(config.restart_state_path);
free(config.fake_outputs);
}
/*
* (Re-)loads the configuration file (sets useful defaults before).
*
* If you specify override_configpath, only this path is used to look for a
* configuration file.
*
* load_type specifies the type of loading: C_VALIDATE is used to only verify
* the correctness of the config file (used with the flag -C). C_LOAD will load
* the config for normal use and display errors in the nagbar. C_RELOAD will
* also clear the previous config.
*
*/
bool load_configuration(const char *override_configpath, config_load_t load_type) {
if (load_type == C_RELOAD) {
free_configuration();
}
SLIST_INIT(&modes);
@ -202,7 +189,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
/* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config));
/* Initialize default colors */
/* Initialize default colors */
#define INIT_COLOR(x, cborder, cbackground, ctext, cindicator) \
do { \
x.border = draw_util_hex_to_color(cborder); \
@ -241,24 +228,32 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
config.focus_wrapping = FOCUS_WRAPPING_ON;
parse_configuration(override_configpath, true);
if (reload) {
translate_keysyms();
grab_all_keys(conn);
regrab_all_buttons(conn);
FREE(current_configpath);
current_configpath = get_config_path(override_configpath, true);
if (current_configpath == NULL) {
die("Unable to find the configuration file (looked at "
"$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
"and " SYSCONFDIR "/i3/config)");
}
LOG("Parsing configfile %s\n", current_configpath);
const bool result = parse_file(current_configpath, load_type != C_VALIDATE);
if (config.font.type == FONT_TYPE_NONE) {
if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) {
ELOG("You did not specify required configuration option \"font\"\n");
config.font = load_font("fixed", true);
set_font(&config.font);
}
/* Redraw the currently visible decorations on reload, so that
* the possibly new drawing parameters changed. */
if (reload) {
if (load_type == C_RELOAD) {
translate_keysyms();
grab_all_keys(conn);
regrab_all_buttons(conn);
/* Redraw the currently visible decorations on reload, so that the
* possibly new drawing parameters changed. */
x_deco_recurse(croot);
xcb_flush(conn);
}
return result;
}

View File

@ -122,7 +122,7 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
}
CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
if (strcmp(modename, DEFAULT_BINDING_MODE) == 0) {
ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
return;
}
@ -334,12 +334,18 @@ CFGFUN(show_marks, const char *value) {
config.show_marks = eval_boolstr(value);
}
CFGFUN(workspace, const char *workspace, const char *outputs) {
DLOG("Assigning workspace \"%s\" to outputs \"%s\"\n", workspace, outputs);
/* Check for earlier assignments of the same workspace so that we
* dont have assignments of a single workspace to different
* outputs */
static char *current_workspace = NULL;
CFGFUN(workspace, const char *workspace, const char *output) {
struct Workspace_Assignment *assignment;
/* When a new workspace line is encountered, for the first output word,
* $workspace from the config.spec is non-NULL. Afterwards, the parser calls
* clear_stack() because of the call line. Thus, we have to preserve the
* workspace string. */
if (workspace) {
FREE(current_workspace);
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
@ -348,16 +354,21 @@ CFGFUN(workspace, const char *workspace, const char *outputs) {
}
}
char *buf = sstrdup(outputs);
char *output = strtok(buf, " ");
while (output != NULL) {
current_workspace = sstrdup(workspace);
} else {
if (!current_workspace) {
DLOG("Both workspace and current_workspace are NULL, assuming we had an error before\n");
return;
}
workspace = current_workspace;
}
DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output);
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = sstrdup(output);
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
output = strtok(NULL, " ");
}
free(buf);
}
CFGFUN(ipc_socket, const char *path) {

View File

@ -409,7 +409,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
next_state(token);
token_handled = true;
/* To make sure we start with an appropriate matching
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
* every command. */

View File

@ -55,6 +55,13 @@ static yajl_callbacks version_callbacks = {
*
*/
void display_running_version(void) {
if (getenv("DISPLAY") == NULL) {
fprintf(stderr, "\nYour DISPLAY environment variable is not set.\n");
fprintf(stderr, "Are you running i3 via SSH or on a virtual console?\n");
fprintf(stderr, "Try DISPLAY=:0 i3 --moreversion\n");
exit(EXIT_FAILURE);
}
char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
if (pid_from_atom == NULL) {
/* If I3_PID is not set, the running version is older than 4.2-200. */

View File

@ -11,6 +11,11 @@
xcb_window_t ewmh_window;
#define FOREACH_NONINTERNAL \
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) \
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) \
if (!con_is_internal(ws))
/*
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
*
@ -19,28 +24,34 @@ xcb_window_t ewmh_window;
*
*/
void ewmh_update_current_desktop(void) {
static uint32_t old_idx = NET_WM_DESKTOP_NONE;
const uint32_t idx = ewmh_get_workspace_index(focused);
if (idx != NET_WM_DESKTOP_NONE) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx);
if (idx == old_idx || idx == NET_WM_DESKTOP_NONE) {
return;
}
old_idx = idx;
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx);
}
/*
* Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of
* noninternal workspaces.
*/
void ewmh_update_number_of_desktops(void) {
Con *output;
static void ewmh_update_number_of_desktops(void) {
Con *output, *ws;
static uint32_t old_idx = 0;
uint32_t idx = 0;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
++idx;
}
FOREACH_NONINTERNAL {
idx++;
};
if (idx == old_idx) {
return;
}
old_idx = idx;
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_NUMBER_OF_DESKTOPS, XCB_ATOM_CARDINAL, 32, 1, &idx);
@ -50,35 +61,24 @@ void ewmh_update_number_of_desktops(void) {
* Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
* list of NULL-terminated strings in UTF-8 encoding"
*/
void ewmh_update_desktop_names(void) {
Con *output;
static void ewmh_update_desktop_names(void) {
Con *output, *ws;
int msg_length = 0;
/* count the size of the property message to set */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
FOREACH_NONINTERNAL {
msg_length += strlen(ws->name) + 1;
}
}
};
char desktop_names[msg_length];
int current_position = 0;
/* fill the buffer with the names of the i3 workspaces */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
FOREACH_NONINTERNAL {
for (size_t i = 0; i < strlen(ws->name) + 1; i++) {
desktop_names[current_position++] = ws->name[i];
}
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_DESKTOP_NAMES, A_UTF8_STRING, 8, msg_length, desktop_names);
@ -88,39 +88,39 @@ void ewmh_update_desktop_names(void) {
* Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
* define the top left corner of each desktop's viewport.
*/
void ewmh_update_desktop_viewport(void) {
Con *output;
static void ewmh_update_desktop_viewport(void) {
Con *output, *ws;
int num_desktops = 0;
/* count number of desktops */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
FOREACH_NONINTERNAL {
num_desktops++;
}
}
uint32_t viewports[num_desktops * 2];
int current_position = 0;
/* fill the viewport buffer */
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *ws;
TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
if (STARTS_WITH(ws->name, "__"))
continue;
FOREACH_NONINTERNAL {
viewports[current_position++] = output->rect.x;
viewports[current_position++] = output->rect.y;
}
}
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports);
}
/*
* Updates all the EWMH desktop properties.
*
*/
void ewmh_update_desktop_properties(void) {
ewmh_update_number_of_desktops();
ewmh_update_desktop_viewport();
ewmh_update_current_desktop();
ewmh_update_desktop_names();
ewmh_update_wm_desktop();
}
static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) {
Con *child;
@ -354,18 +354,12 @@ Con *ewmh_get_workspace_by_index(uint32_t idx) {
uint32_t current_index = 0;
Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *workspace;
TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) {
if (con_is_internal(workspace))
continue;
if (current_index == idx)
return workspace;
++current_index;
Con *output, *ws;
FOREACH_NONINTERNAL {
if (current_index == idx) {
return ws;
}
current_index++;
}
return NULL;
@ -381,19 +375,14 @@ Con *ewmh_get_workspace_by_index(uint32_t idx) {
uint32_t ewmh_get_workspace_index(Con *con) {
uint32_t index = 0;
Con *workspace = con_get_workspace(con);
Con *output;
TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
Con *current;
TAILQ_FOREACH(current, &(output_get_content(output)->nodes_head), nodes) {
if (con_is_internal(current))
continue;
if (current == workspace)
Con *target_workspace = con_get_workspace(con);
Con *output, *ws;
FOREACH_NONINTERNAL {
if (ws == target_workspace) {
return index;
++index;
}
index++;
}
return NET_WM_DESKTOP_NONE;

View File

@ -74,7 +74,7 @@ void fake_outputs_init(const char *output_spec) {
else
TAILQ_INSERT_TAIL(&outputs, new_output, outputs);
output_init_con(new_output);
init_ws_for_output(new_output, output_get_content(new_output->con));
init_ws_for_output(new_output);
num_screens++;
}
new_output->primary = primary;

View File

@ -60,12 +60,17 @@ static void floating_set_hint_atom(Con *con, bool floating) {
}
/*
* Called when a floating window is created or resized.
* This function resizes the window if its size is higher or lower than the
* configured maximum/minimum size, respectively.
* Called when a floating window is created or resized. This function resizes
* the window if its size is higher or lower than the configured maximum/minimum
* size, respectively or when adjustments are needed to conform to the
* configured size increments or aspect ratio limits.
*
* When prefer_height is true and the window needs to be resized because of the
* configured aspect ratio, the width is adjusted first, preserving the previous
* height.
*
*/
void floating_check_size(Con *floating_con) {
void floating_check_size(Con *floating_con, bool prefer_height) {
/* Define reasonable minimal and maximal sizes for floating windows */
const int floating_sane_min_height = 50;
const int floating_sane_min_width = 75;
@ -83,43 +88,89 @@ void floating_check_size(Con *floating_con) {
border_rect.height += render_deco_height();
}
if (focused_con->window != NULL) {
if (focused_con->window->min_width) {
i3Window *window = focused_con->window;
if (window != NULL) {
/* ICCCM says: If a base size is not provided, the minimum size is to be used in its place
* and vice versa. */
int min_width = (window->min_width ? window->min_width : window->base_width);
int min_height = (window->min_height ? window->min_height : window->base_height);
int base_width = (window->base_width ? window->base_width : window->min_width);
int base_height = (window->base_height ? window->base_height : window->min_height);
if (min_width) {
floating_con->rect.width -= border_rect.width;
floating_con->rect.width = max(floating_con->rect.width, focused_con->window->min_width);
floating_con->rect.width = max(floating_con->rect.width, min_width);
floating_con->rect.width += border_rect.width;
}
if (focused_con->window->min_height) {
if (min_height) {
floating_con->rect.height -= border_rect.height;
floating_con->rect.height = max(floating_con->rect.height, focused_con->window->min_height);
floating_con->rect.height = max(floating_con->rect.height, min_height);
floating_con->rect.height += border_rect.height;
}
if (focused_con->window->max_width) {
if (window->max_width) {
floating_con->rect.width -= border_rect.width;
floating_con->rect.width = min(floating_con->rect.width, focused_con->window->max_width);
floating_con->rect.width = min(floating_con->rect.width, window->max_width);
floating_con->rect.width += border_rect.width;
}
if (focused_con->window->max_height) {
if (window->max_height) {
floating_con->rect.height -= border_rect.height;
floating_con->rect.height = min(floating_con->rect.height, focused_con->window->max_height);
floating_con->rect.height = min(floating_con->rect.height, window->max_height);
floating_con->rect.height += border_rect.height;
}
if (focused_con->window->height_increment &&
floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
floating_con->rect.height -= focused_con->window->base_height + border_rect.height;
floating_con->rect.height -= floating_con->rect.height % focused_con->window->height_increment;
floating_con->rect.height += focused_con->window->base_height + border_rect.height;
/* Obey the aspect ratio, if any, unless we are in fullscreen mode.
*
* The spec isnt explicit on whether the aspect ratio hints should be
* respected during fullscreen mode. Other WMs such as Openbox dont do
* that, and this post suggests that this is the correct way to do it:
* https://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
*
* Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
* subtitle rendering, see https://bugs.i3wm.org/594 */
const double min_ar = window->min_aspect_ratio;
const double max_ar = window->max_aspect_ratio;
if (floating_con->fullscreen_mode == CF_NONE && (min_ar > 0 || max_ar > 0)) {
/* The ICCCM says to subtract the base size from the window size for
* aspect ratio calculations. However, unlike determining the base
* size itself we must not fall back to using the minimum size in
* this case according to the ICCCM. */
double width = floating_con->rect.width - window->base_width - border_rect.width;
double height = floating_con->rect.height - window->base_height - border_rect.height;
const double ar = (double)width / (double)height;
double new_ar = -1;
if (min_ar > 0 && ar < min_ar) {
new_ar = min_ar;
} else if (max_ar > 0 && ar > max_ar) {
new_ar = max_ar;
}
if (new_ar > 0) {
if (prefer_height) {
width = round(height * new_ar);
height = round(width / new_ar);
} else {
height = round(width / new_ar);
width = round(height * new_ar);
}
floating_con->rect.width = width + window->base_width + border_rect.width;
floating_con->rect.height = height + window->base_height + border_rect.height;
}
}
if (focused_con->window->width_increment &&
floating_con->rect.width >= focused_con->window->base_width + border_rect.width) {
floating_con->rect.width -= focused_con->window->base_width + border_rect.width;
floating_con->rect.width -= floating_con->rect.width % focused_con->window->width_increment;
floating_con->rect.width += focused_con->window->base_width + border_rect.width;
if (window->height_increment &&
floating_con->rect.height >= base_height + border_rect.height) {
floating_con->rect.height -= base_height + border_rect.height;
floating_con->rect.height -= floating_con->rect.height % window->height_increment;
floating_con->rect.height += base_height + border_rect.height;
}
if (window->width_increment &&
floating_con->rect.width >= base_width + border_rect.width) {
floating_con->rect.width -= base_width + border_rect.width;
floating_con->rect.width -= floating_con->rect.width % window->width_increment;
floating_con->rect.width += base_width + border_rect.width;
}
}
@ -313,7 +364,7 @@ void floating_enable(Con *con, bool automatic) {
nc->rect.height += con->border_width * 2;
nc->rect.width += con->border_width * 2;
floating_check_size(nc);
floating_check_size(nc, false);
/* Some clients (like GIMPs color picker window) get mapped
* to (0, 0), so we push them to a reasonable position
@ -361,8 +412,7 @@ void floating_enable(Con *con, bool automatic) {
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
/* render the cons to get initial window_rect correct */
render_con(nc, false);
render_con(con, false);
render_con(nc);
if (set_focus)
con_activate(con);
@ -517,7 +567,7 @@ DRAGGING_CB(drag_window_callback) {
con->rect.x = old_rect->x + (new_x - event->root_x);
con->rect.y = old_rect->y + (new_y - event->root_y);
render_con(con, false);
render_con(con);
x_push_node(con);
xcb_flush(conn);
@ -611,7 +661,7 @@ DRAGGING_CB(resize_window_callback) {
con->rect = (Rect){dest_x, dest_y, dest_width, dest_height};
/* Obey window size */
floating_check_size(con);
floating_check_size(con, false);
/* If not the lower right corner is grabbed, we must also reposition
* the client by exactly the amount we resized it */
@ -624,9 +674,7 @@ DRAGGING_CB(resize_window_callback) {
con->rect.x = dest_x;
con->rect.y = dest_y;
/* TODO: dont re-render the whole tree just because we change
* coordinates of a floating window */
tree_render();
render_con(con);
x_push_changes(croot);
}
@ -919,7 +967,7 @@ bool floating_reposition(Con *con, Rect newrect) {
* window's size hints.
*
*/
void floating_resize(Con *floating_con, int x, int y) {
void floating_resize(Con *floating_con, uint32_t x, uint32_t y) {
DLOG("floating resize to %dx%d px\n", x, y);
Rect *rect = &floating_con->rect;
Con *focused_con = con_descend_focused(floating_con);
@ -929,6 +977,7 @@ void floating_resize(Con *floating_con, int x, int y) {
}
int wi = focused_con->window->width_increment;
int hi = focused_con->window->height_increment;
bool prefer_height = (rect->width == x);
rect->width = x;
rect->height = y;
if (wi)
@ -936,7 +985,7 @@ void floating_resize(Con *floating_con, int x, int y) {
if (hi)
rect->height += (hi - 1 - rect->height) % hi;
floating_check_size(floating_con);
floating_check_size(floating_con, prefer_height);
/* If this is a scratchpad window, don't auto center it from now on. */
if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)

View File

@ -20,6 +20,7 @@
int randr_base = -1;
int xkb_base = -1;
int xkb_current_group;
int shape_base = -1;
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -974,133 +975,15 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
return false;
}
xcb_size_hints_t size_hints;
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL) {
xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
} else {
xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
}
int win_width = con->window_rect.width;
int win_height = con->window_rect.height;
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
con->window->min_width = size_hints.min_width;
con->window->min_height = size_hints.min_height;
}
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
con->window->max_width = size_hints.max_width;
con->window->max_height = size_hints.max_height;
}
if (con_is_floating(con)) {
win_width = MAX(win_width, con->window->min_width);
win_height = MAX(win_height, con->window->min_height);
win_width = MIN(win_width, con->window->max_width);
win_height = MIN(win_height, con->window->max_height);
}
bool changed = false;
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
if (con->window->width_increment != size_hints.width_inc) {
con->window->width_increment = size_hints.width_inc;
changed = true;
}
}
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
if (con->window->height_increment != size_hints.height_inc) {
con->window->height_increment = size_hints.height_inc;
changed = true;
}
}
bool changed = window_update_normal_hints(con->window, reply, NULL);
if (changed) {
DLOG("resize increments changed\n");
}
}
bool has_base_size = false;
int base_width = 0;
int base_height = 0;
/* The base width / height is the desired size of the window. */
if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
base_width = size_hints.base_width;
base_height = size_hints.base_height;
has_base_size = true;
}
/* If the window didn't specify a base size, the ICCCM tells us to fall
* back to the minimum size instead, if available. */
if (!has_base_size && size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
base_width = size_hints.min_width;
base_height = size_hints.min_height;
}
// TODO XXX Should we only do this is the base size is > 0?
if (base_width != con->window->base_width || base_height != con->window->base_height) {
con->window->base_width = base_width;
con->window->base_height = base_height;
DLOG("client's base_height changed to %d\n", base_height);
DLOG("client's base_width changed to %d\n", base_width);
changed = true;
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) ||
(size_hints.min_aspect_num <= 0) ||
(size_hints.min_aspect_den <= 0)) {
goto render_and_return;
}
/* The ICCCM says to subtract the base size from the window size for aspect
* ratio calculations. However, unlike determining the base size itself we
* must not fall back to using the minimum size in this case according to
* the ICCCM. */
double width = win_width - base_width * has_base_size;
double height = win_height - base_height * has_base_size;
/* Convert numerator/denominator to a double */
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
DLOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) {
goto render_and_return;
}
/* Check if we need to set proportional_* variables using the correct ratio */
double aspect_ratio = 0.0;
if ((width / height) < min_aspect) {
aspect_ratio = min_aspect;
} else if ((width / height) > max_aspect) {
aspect_ratio = max_aspect;
} else {
goto render_and_return;
}
if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
con->window->aspect_ratio = aspect_ratio;
changed = true;
}
render_and_return:
if (changed) {
Con *floating = con_inside_floating(con);
if (floating) {
floating_check_size(con, false);
tree_render();
}
}
FREE(reply);
return true;
@ -1517,6 +1400,27 @@ void handle_event(int type, xcb_generic_event_t *event) {
return;
}
if (shape_supported && type == shape_base + XCB_SHAPE_NOTIFY) {
xcb_shape_notify_event_t *shape = (xcb_shape_notify_event_t *)event;
DLOG("shape_notify_event for window 0x%08x, shape_kind = %d, shaped = %d\n",
shape->affected_window, shape->shape_kind, shape->shaped);
Con *con = con_by_window_id(shape->affected_window);
if (con == NULL) {
LOG("Not a managed window 0x%08x, ignoring shape_notify_event\n",
shape->affected_window);
return;
}
if (shape->shape_kind == XCB_SHAPE_SK_BOUNDING ||
shape->shape_kind == XCB_SHAPE_SK_INPUT) {
x_set_shape(con, shape->shape_kind, shape->shaped);
}
return;
}
switch (type) {
case XCB_KEY_PRESS:
case XCB_KEY_RELEASE:

View File

@ -33,6 +33,9 @@ all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
*/
static void set_nonblock(int sockfd) {
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags & O_NONBLOCK) {
return;
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) < 0)
err(-1, "Could not set O_NONBLOCK");
@ -125,9 +128,11 @@ static void ipc_send_client_message(ipc_client *client, size_t size, const uint3
}
}
static void free_ipc_client(ipc_client *client) {
static void free_ipc_client(ipc_client *client, int exempt_fd) {
if (client->fd != exempt_fd) {
DLOG("Disconnecting client on fd %d\n", client->fd);
close(client->fd);
}
ev_io_stop(main_loop, client->read_callback);
FREE(client->read_callback);
@ -195,15 +200,19 @@ static void ipc_send_shutdown_event(shutdown_reason_t reason) {
* Calls shutdown() on each socket and closes it. This function is to be called
* when exiting or restarting only!
*
* exempt_fd is never closed. Set to -1 to close all fds.
*
*/
void ipc_shutdown(shutdown_reason_t reason) {
void ipc_shutdown(shutdown_reason_t reason, int exempt_fd) {
ipc_send_shutdown_event(reason);
ipc_client *current;
while (!TAILQ_EMPTY(&all_clients)) {
current = TAILQ_FIRST(&all_clients);
if (current->fd != exempt_fd) {
shutdown(current->fd, SHUT_RDWR);
free_ipc_client(current);
}
free_ipc_client(current, exempt_fd);
}
}
@ -215,12 +224,11 @@ void ipc_shutdown(shutdown_reason_t reason) {
IPC_HANDLER(run_command) {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size + 1, 1);
strncpy(command, (const char *)message, message_size);
char *command = sstrndup((const char *)message, message_size);
LOG("IPC: received: *%s*\n", command);
yajl_gen gen = yajl_gen_alloc(NULL);
CommandResult *result = parse_command((const char *)command, gen);
CommandResult *result = parse_command(command, gen, client);
free(command);
if (result->needs_tree_render)
@ -649,6 +657,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(integer, con->depth);
}
if (inplace_restart && con->type == CT_ROOT && previous_workspace_name) {
ystr("previous_workspace_name");
ystr(previous_workspace_name);
}
y(map_close);
}
@ -1335,7 +1348,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
/* If not, there was some kind of error. We dont bother and close the
* connection. Delete the client from the list of clients. */
free_ipc_client(client);
free_ipc_client(client, -1);
FREE(message);
return;
}
@ -1393,7 +1406,7 @@ end:
ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
}
free_ipc_client(client);
free_ipc_client(client, -1);
}
static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) {
@ -1427,6 +1440,18 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
/* Close this file descriptor on exec() */
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
ipc_new_client_on_fd(EV_A_ fd);
}
/*
* ipc_new_client_on_fd() only sets up the event handler
* for activity on the new connection and inserts the file descriptor into
* the list of clients.
*
* This variant is useful for the inherited IPC connection when restarting.
*
*/
ipc_client *ipc_new_client_on_fd(EV_P_ int fd) {
set_nonblock(fd);
ipc_client *client = scalloc(1, sizeof(ipc_client));
@ -1441,8 +1466,9 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
client->write_callback->data = client;
ev_io_init(client->write_callback, ipc_socket_writeable_cb, fd, EV_WRITE);
DLOG("IPC: new client connected on fd %d\n", w->fd);
DLOG("IPC: new client connected on fd %d\n", fd);
TAILQ_INSERT_TAIL(&all_clients, client, clients);
return client;
}
/*
@ -1623,3 +1649,15 @@ void ipc_send_binding_event(const char *event_type, Binding *bind) {
y(free);
setlocale(LC_NUMERIC, "");
}
/*
* Sends a restart reply to the IPC client on the specified fd.
*/
void ipc_confirm_restart(ipc_client *client) {
DLOG("ipc_confirm_restart(fd %d)\n", client->fd);
static const char *reply = "[{\"success\":true}]";
ipc_send_client_message(
client, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND,
(const uint8_t *)reply);
ipc_push_pending(client);
}

View File

@ -31,7 +31,15 @@ static bool parsing_marks;
struct Match *current_swallow;
static bool swallow_is_empty;
static int num_marks;
static char **marks;
/* We need to save each container that needs to be marked if we want to support
* marking non-leaf containers. In their case, the end_map for their children is
* called before their own end_map, so marking json_node would end up marking
* the latest child. We can't just mark containers immediately after we parse a
* mark because of #2511. */
struct pending_marks {
char *mark;
Con *con_to_be_marked;
} * marks;
/* This list is used for reordering the focus stack after parsing the 'focus'
* array. */
@ -144,13 +152,15 @@ static int json_end_map(void *ctx) {
}
}
floating_check_size(json_node);
floating_check_size(json_node, false);
}
if (num_marks > 0) {
for (int i = 0; i < num_marks; i++) {
con_mark(json_node, marks[i], MM_ADD);
free(marks[i]);
Con *con = marks[i].con_to_be_marked;
char *mark = marks[i].mark;
con_mark(con, mark, MM_ADD);
free(mark);
}
FREE(marks);
@ -274,8 +284,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
char *mark;
sasprintf(&mark, "%.*s", (int)len, val);
marks = srealloc(marks, (++num_marks) * sizeof(char *));
marks[num_marks - 1] = sstrdup(mark);
marks = srealloc(marks, (++num_marks) * sizeof(struct pending_marks));
marks[num_marks - 1].mark = sstrdup(mark);
marks[num_marks - 1].con_to_be_marked = json_node;
} else {
if (strcasecmp(last_key, "name") == 0) {
json_node->name = scalloc(len + 1, 1);
@ -409,6 +420,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
else if (strcasecmp(buf, "changed") == 0)
json_node->scratchpad_state = SCRATCHPAD_CHANGED;
free(buf);
} else if (strcasecmp(last_key, "previous_workspace_name") == 0) {
FREE(previous_workspace_name);
previous_workspace_name = sstrndup((const char *)val, len);
}
}
return 1;

View File

@ -89,6 +89,7 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
/* We hope that those are supported and set them to true */
bool xcursor_supported = true;
bool xkb_supported = true;
bool shape_supported = true;
bool force_xinerama = false;
@ -165,7 +166,7 @@ static void i3_exit(void) {
fflush(stderr);
shm_unlink(shmlogname);
}
ipc_shutdown(SHUTDOWN_REASON_EXIT);
ipc_shutdown(SHUTDOWN_REASON_EXIT, -1);
unlink(config.ipc_socket_path);
xcb_disconnect(conn);
@ -235,6 +236,20 @@ static void setup_term_handlers(void) {
}
}
static int parse_restart_fd(void) {
const char *restart_fd = getenv("_I3_RESTART_FD");
if (restart_fd == NULL) {
return -1;
}
long int fd = -1;
if (!parse_long(restart_fd, &fd, 10)) {
ELOG("Malformed _I3_RESTART_FD \"%s\"\n", restart_fd);
return -1;
}
return fd;
}
int main(int argc, char *argv[]) {
/* Keep a symbol pointing to the I3_VERSION string constant so that we have
* it in gdb backtraces. */
@ -419,7 +434,7 @@ int main(int argc, char *argv[]) {
}
if (only_check_config) {
exit(parse_configuration(override_configpath, false) ? 0 : 1);
exit(load_configuration(override_configpath, C_VALIDATE) ? 0 : 1);
}
/* If the user passes more arguments, we act like i3-msg would: Just send
@ -519,7 +534,7 @@ int main(int argc, char *argv[]) {
conn = xcb_connect(NULL, &conn_screen);
if (xcb_connection_has_error(conn))
errx(EXIT_FAILURE, "Cannot open display\n");
errx(EXIT_FAILURE, "Cannot open display");
sndisplay = sn_xcb_display_new(conn, NULL, NULL);
@ -533,7 +548,7 @@ int main(int argc, char *argv[]) {
root_screen = xcb_aux_get_screen(conn, conn_screen);
root = root_screen->root;
/* Place requests for the atoms we need as soon as possible */
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
#include "atoms.xmacro"
@ -571,7 +586,7 @@ int main(int argc, char *argv[]) {
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
/* Setup NetWM atoms */
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
@ -585,7 +600,7 @@ int main(int argc, char *argv[]) {
#include "atoms.xmacro"
#undef xmacro
load_configuration(conn, override_configpath, false);
load_configuration(override_configpath, C_LOAD);
if (config.ipc_socket_path == NULL) {
/* Fall back to a file name in /tmp/ based on the PID */
@ -627,6 +642,9 @@ int main(int argc, char *argv[]) {
xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
const xcb_query_extension_reply_t *extreply;
xcb_prefetch_extension_data(conn, &xcb_xkb_id);
xcb_prefetch_extension_data(conn, &xcb_shape_id);
extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
xkb_supported = extreply->present;
if (!extreply->present) {
@ -688,6 +706,23 @@ int main(int argc, char *argv[]) {
xkb_base = extreply->first_event;
}
/* Check for Shape extension. We want to handle input shapes which is
* introduced in 1.1. */
extreply = xcb_get_extension_data(conn, &xcb_shape_id);
if (extreply->present) {
shape_base = extreply->first_event;
xcb_shape_query_version_cookie_t cookie = xcb_shape_query_version(conn);
xcb_shape_query_version_reply_t *version =
xcb_shape_query_version_reply(conn, cookie, NULL);
shape_supported = version && version->minor_version >= 1;
free(version);
} else {
shape_supported = false;
}
if (!shape_supported) {
DLOG("shape 1.1 is not present on this server\n");
}
restore_connect();
property_handlers_init();
@ -826,15 +861,21 @@ int main(int argc, char *argv[]) {
}
}
{
const int restart_fd = parse_restart_fd();
if (restart_fd != -1) {
DLOG("serving restart fd %d", restart_fd);
ipc_client *client = ipc_new_client_on_fd(main_loop, restart_fd);
ipc_confirm_restart(client);
}
}
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
x_set_i3_atoms();
ewmh_update_workarea();
/* Set the ewmh desktop properties. */
ewmh_update_current_desktop();
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_desktop_properties();
struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
xcb_prepare = scalloc(1, sizeof(struct ev_prepare));

View File

@ -80,6 +80,8 @@ void restore_geometry(void) {
*/
void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
bool needs_to_be_mapped) {
DLOG("window 0x%08x\n", window);
xcb_drawable_t d = {window};
xcb_get_geometry_cookie_t geomc;
xcb_get_geometry_reply_t *geom;
@ -163,8 +165,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
DLOG("Managing window 0x%08x\n", window);
i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window;
cwindow->depth = get_visual_depth(attr->visual);
@ -185,9 +185,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
border_style_t motif_border_style = BS_NORMAL;
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
xcb_size_hints_t wm_size_hints;
if (!xcb_icccm_get_wm_size_hints_reply(conn, wm_normal_hints_cookie, &wm_size_hints, NULL))
memset(&wm_size_hints, '\0', sizeof(xcb_size_hints_t));
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
@ -437,10 +435,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) ||
xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_SPLASH) ||
xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_MODAL) ||
(wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE &&
wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE &&
wm_size_hints.min_height == wm_size_hints.max_height &&
wm_size_hints.min_width == wm_size_hints.max_width)) {
(cwindow->max_width > 0 && cwindow->max_height > 0 &&
cwindow->min_height == cwindow->max_height &&
cwindow->min_width == cwindow->max_width)) {
LOG("This window is a dialog window, setting floating\n");
want_floating = true;
}
@ -499,29 +496,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if (cwindow->dock)
want_floating = false;
/* Plasma windows set their geometry in WM_SIZE_HINTS. */
if ((wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
(wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
DLOG("We are setting geometry according to wm_size_hints x=%d y=%d w=%d h=%d\n",
wm_size_hints.x, wm_size_hints.y, wm_size_hints.width, wm_size_hints.height);
geom->x = wm_size_hints.x;
geom->y = wm_size_hints.y;
geom->width = wm_size_hints.width;
geom->height = wm_size_hints.height;
}
if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height);
nc->window->min_width = wm_size_hints.min_width;
nc->window->min_height = wm_size_hints.min_height;
}
if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
DLOG("Window specifies maximum size %d x %d\n", wm_size_hints.max_width, wm_size_hints.max_height);
nc->window->max_width = wm_size_hints.max_width;
nc->window->max_height = wm_size_hints.max_height;
}
/* Store the requested geometry. The width/height gets raised to at least
* 75x50 when entering floating mode, which is the minimum size for a
* window to be useful (smaller windows are usually overlays/toolbars/
@ -574,6 +548,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* cleanup) */
xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
if (shape_supported) {
/* Receive ShapeNotify events whenever the client altered its window
* shape. */
xcb_shape_select_input(conn, window, true);
/* Check if the window is shaped. Sadly, we can check only for the
* bounding shape, not for the input shape. */
xcb_shape_query_extents_cookie_t cookie =
xcb_shape_query_extents(conn, window);
xcb_shape_query_extents_reply_t *reply =
xcb_shape_query_extents_reply(conn, cookie, NULL);
if (reply != NULL && reply->bounding_shaped) {
cwindow->shaped = true;
}
FREE(reply);
}
/* Check if any assignments match */
run_assignments(cwindow);
@ -595,13 +586,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
* workspace at all. However, just calling render_con() on the
* workspace isnt enough either it needs the rect. */
ws->rect = ws->parent->rect;
render_con(ws, true);
render_con(ws);
/* Disable setting focus, otherwise wed move focus to an invisible
* workspace, which we generally prevent (e.g. in
* con_move_to_workspace). */
set_focus = false;
}
render_con(croot, false);
render_con(croot);
/* Send an event about window creation */
ipc_send_window_event("new", nc);

View File

@ -54,16 +54,8 @@ char *output_primary_name(Output *output) {
Output *get_output_for_con(Con *con) {
Con *output_con = con_get_output(con);
if (output_con == NULL) {
ELOG("Could not get the output container for con = %p.\n", con);
return NULL;
}
Output *output = get_output_by_name(output_con->name, true);
if (output == NULL) {
ELOG("Could not get output from name \"%s\".\n", output_con->name);
return NULL;
}
assert(output != NULL);
return output;
}

View File

@ -420,7 +420,10 @@ void output_init_con(Output *output) {
* Create the first unused workspace.
*
*/
void init_ws_for_output(Output *output, Con *content) {
void init_ws_for_output(Output *output) {
Con *content = output_get_content(output->con);
Con *previous_focus = con_get_workspace(focused);
/* go through all assignments and move the existing workspaces to this output */
struct Workspace_Assignment *assignment;
TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
@ -442,48 +445,17 @@ void init_ws_for_output(Output *output, Con *content) {
continue;
}
/* if so, move it over */
LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
DLOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n",
workspace->name, workspace_out->name, output_primary_name(output));
/* if the workspace is currently visible on that output, we need to
* switch to a different workspace - otherwise the output would end up
* with no active workspace */
bool visible = workspace_is_visible(workspace);
Con *previous = NULL;
if (visible && (previous = TAILQ_NEXT(workspace, focused))) {
LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n",
previous->name, workspace_out->name);
workspace_show(previous);
/* Need to copy output's rect since content is not yet rendered. We
* can't call render_con here because render_output only proceeds if a
* workspace exists. */
content->rect = output->con->rect;
workspace_move_to_output(workspace, output);
}
/* Render the output on which the workspace was to get correct Rects.
* Then, we need to work with the "content" container, since we cannot
* be sure that the workspace itself was rendered at all (in case its
* invisible, it wont be rendered). */
render_con(workspace_out, false);
Con *ws_out_content = output_get_content(workspace_out);
Con *floating_con;
TAILQ_FOREACH(floating_con, &(workspace->floating_head), floating_windows)
/* NB: We use output->con here because content is not yet rendered,
* so it has a rect of {0, 0, 0, 0}. */
floating_fix_coordinates(floating_con, &(ws_out_content->rect), &(output->con->rect));
con_detach(workspace);
con_attach(workspace, content, false);
/* In case the workspace we just moved was visible but there was no
* other workspace to switch to, we need to initialize the source
* output as well */
if (visible && previous == NULL) {
LOG("There is no workspace left on \"%s\", re-initializing\n",
workspace_out->name);
init_ws_for_output(get_output_by_name(workspace_out->name, true),
output_get_content(workspace_out));
DLOG("Done re-initializing, continuing with \"%s\"\n", output_primary_name(output));
}
}
/* Temporarily set the focused container, might not be initialized yet. */
focused = content;
/* if a workspace exists, we are done now */
if (!TAILQ_EMPTY(&(content->nodes_head))) {
@ -493,10 +465,9 @@ void init_ws_for_output(Output *output, Con *content) {
GREP_FIRST(visible, content, child->fullscreen_mode == CF_OUTPUT);
if (!visible) {
visible = TAILQ_FIRST(&(content->nodes_head));
focused = content;
workspace_show(visible);
}
return;
goto restore_focus;
}
/* otherwise, we create the first assigned ws for this output */
@ -507,17 +478,18 @@ void init_ws_for_output(Output *output, Con *content) {
LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n",
assignment->name, assignment->output);
focused = content;
workspace_show_by_name(assignment->name);
return;
goto restore_focus;
}
/* if there is still no workspace, we create the first free workspace */
DLOG("Now adding a workspace\n");
Con *ws = create_workspace_on_output(output, content);
workspace_show(create_workspace_on_output(output, content));
/* TODO: Set focus in main.c */
con_focus(ws);
restore_focus:
if (previous_focus) {
workspace_show(previous_focus);
}
}
/*
@ -937,7 +909,7 @@ void randr_query_outputs(void) {
if (!TAILQ_EMPTY(&(content->nodes_head)))
continue;
DLOG("Should add ws for output %s\n", output_primary_name(output));
init_ws_for_output(output, content);
init_ws_for_output(output);
}
/* Focus the primary screen, if possible */
@ -976,13 +948,8 @@ void randr_disable_output(Output *output) {
if (output->con != NULL) {
/* We need to move the workspaces from the disappearing output to the first output */
/* 1: Get the con to focus next, if the disappearing ws is focused */
Con *next = NULL;
if (TAILQ_FIRST(&(croot->focus_head)) == output->con) {
DLOG("This output (%p) was focused! Getting next\n", output->con);
next = focused;
DLOG("next = %p\n", next);
}
/* 1: Get the con to focus next */
Con *next = focused;
/* 2: iterate through workspaces and re-assign them, fixing the coordinates
* of floating containers as we go */
@ -1005,13 +972,12 @@ void randr_disable_output(Output *output) {
TAILQ_FOREACH(floating_con, &(current->floating_head), floating_windows) {
floating_fix_coordinates(floating_con, &(output->con->rect), &(first->con->rect));
}
DLOG("Done, next\n");
}
DLOG("re-attached all workspaces\n");
/* Restore focus after con_detach / con_attach. next can be NULL, see #3523. */
if (next) {
DLOG("now focusing next = %p\n", next);
con_activate(next);
con_focus(next);
workspace_show(con_get_workspace(next));
}
@ -1050,7 +1016,7 @@ void randr_disable_output(Output *output) {
static void fallback_to_root_output(void) {
root_output->active = true;
output_init_con(root_output);
init_ws_for_output(root_output, output_get_content(root_output->con));
init_ws_for_output(root_output);
}
/*

View File

@ -37,16 +37,15 @@ int render_deco_height(void) {
* updated in X11.
*
*/
void render_con(Con *con, bool render_fullscreen) {
void render_con(Con *con) {
render_params params = {
.rect = con->rect,
.x = con->rect.x,
.y = con->rect.y,
.children = con_num_children(con)};
DLOG("Rendering %snode %p / %s / layout %d / children %d\n",
(render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
params.children);
DLOG("Rendering node %p / %s / layout %d / children %d\n", con, con->name,
con->layout, params.children);
int i = 0;
con->mapped = true;
@ -57,42 +56,14 @@ void render_con(Con *con, bool render_fullscreen) {
* needs to be smaller */
Rect *inset = &(con->window_rect);
*inset = (Rect){0, 0, con->rect.width, con->rect.height};
if (!render_fullscreen)
if (con->fullscreen_mode == CF_NONE) {
*inset = rect_add(*inset, con_border_style_rect(con));
}
/* Obey x11 border */
inset->width -= (2 * con->border_width);
inset->height -= (2 * con->border_width);
/* Obey the aspect ratio, if any, unless we are in fullscreen mode.
*
* The spec isnt explicit on whether the aspect ratio hints should be
* respected during fullscreen mode. Other WMs such as Openbox dont do
* that, and this post suggests that this is the correct way to do it:
* https://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
*
* Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
* subtitle rendering, see https://bugs.i3wm.org/594 */
if (!render_fullscreen && con->window->aspect_ratio > 0.0) {
DLOG("aspect_ratio = %f, current width/height are %d/%d\n",
con->window->aspect_ratio, inset->width, inset->height);
double new_height = inset->height + 1;
int new_width = inset->width;
while (new_height > inset->height) {
new_height = (1.0 / con->window->aspect_ratio) * new_width;
if (new_height > inset->height)
new_width--;
}
/* Center the window */
inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2);
inset->x += ceil(inset->width / 2) - floor(new_width / 2);
inset->height = new_height + .5;
inset->width = new_width;
}
/* NB: We used to respect resize increment size hints for tiling
* windows up until commit 0db93d9 here. However, since all terminal
* emulators cope with ignoring the size hints in a better way than we
@ -110,7 +81,7 @@ void render_con(Con *con, bool render_fullscreen) {
if (fullscreen) {
fullscreen->rect = params.rect;
x_raise_con(fullscreen);
render_con(fullscreen, true);
render_con(fullscreen);
/* Fullscreen containers are either global (underneath the CT_ROOT
* container) or per-output (underneath the CT_CONTENT container). For
* global fullscreen containers, we cannot abort rendering here yet,
@ -153,7 +124,7 @@ void render_con(Con *con, bool render_fullscreen) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
render_con(child, false);
render_con(child);
i++;
}
@ -164,16 +135,16 @@ void render_con(Con *con, bool render_fullscreen) {
if ((child = TAILQ_FIRST(&(con->focus_head)))) {
/* By rendering the stacked container again, we handle the case
* that we have a non-leaf-container inside the stack. In that
* case, the children of the non-leaf-container need to be raised
* as well. */
render_con(child, false);
* case, the children of the non-leaf-container need to be
* raised as well. */
render_con(child);
}
if (params.children != 1)
/* Raise the stack con itself. This will put the stack decoration on
* top of every stack window. That way, when a new window is opened in
* the stack, the old window will not obscure part of the decoration
* (its unmapped afterwards). */
/* Raise the stack con itself. This will put the stack
* decoration on top of every stack window. That way, when a
* new window is opened in the stack, the old window will not
* obscure part of the decoration (its unmapped afterwards). */
x_raise_con(con);
}
}
@ -192,7 +163,7 @@ static int *precalculate_sizes(Con *con, render_params *p) {
Con *child;
int i = 0, assigned = 0;
int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
int total = con_rect_size_in_orientation(con);
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
assigned += sizes[i++] = lround(percentage * total);
@ -215,7 +186,7 @@ static void render_root(Con *con, Con *fullscreen) {
Con *output;
if (!fullscreen) {
TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
render_con(output, false);
render_con(output);
}
}
@ -281,7 +252,7 @@ static void render_root(Con *con, Con *fullscreen) {
DLOG("floating child at (%d,%d) with %d x %d\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
render_con(child, false);
render_con(child);
}
}
}
@ -331,7 +302,7 @@ static void render_output(Con *con) {
if (fullscreen) {
fullscreen->rect = con->rect;
x_raise_con(fullscreen);
render_con(fullscreen, true);
render_con(fullscreen);
return;
}
@ -371,7 +342,7 @@ static void render_output(Con *con) {
DLOG("child at (%d, %d) with (%d x %d)\n",
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
render_con(child, false);
render_con(child);
}
}

View File

@ -101,30 +101,16 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t dir
return true;
}
/*
* Calculate the given container's new percent given a change in pixels.
*
*/
double px_resize_to_percent(Con *con, int px_diff) {
Con *parent = con->parent;
const orientation_t o = con_orientation(parent);
const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
/* deco_rect.height is subtracted from each child in render_con_split */
const int target = px_diff + (o == HORIZ ? con->rect.width : con->rect.height + con->deco_rect.height);
return ((double)target / (double)total);
}
/*
* Calculate the minimum percent needed for the given container to be at least 1
* pixel.
*
*/
double percent_for_1px(Con *con) {
Con *parent = con->parent;
const orientation_t o = con_orientation(parent);
const int total = (o == HORIZ ? parent->rect.width : parent->rect.height);
const int target = (o == HORIZ ? 1 : 1 + con->deco_rect.height);
return ((double)target / (double)total);
const int parent_size = con_rect_size_in_orientation(con->parent);
/* deco_rect.height is subtracted from each child in render_con_split */
const int min_size = (con_orientation(con->parent) == HORIZ ? 1 : 1 + con->deco_rect.height);
return ((double)min_size / (double)parent_size);
}
/*
@ -145,8 +131,10 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) {
new_first_percent = first->percent + ((double)ppt / 100.0);
new_second_percent = second->percent - ((double)ppt / 100.0);
} else {
new_first_percent = px_resize_to_percent(first, px);
new_second_percent = second->percent + first->percent - new_first_percent;
/* Convert px change to change in percentages */
const double pct = (double)px / (double)con_rect_size_in_orientation(first->parent);
new_first_percent = first->percent + pct;
new_second_percent = second->percent - pct;
}
/* Ensure that no container will be less than 1 pixel in the resizing
* direction. */
@ -234,6 +222,11 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
int pixels = (new_position - initial_position);
DLOG("Done, pixels = %d\n", pixels);
/* No change; no action needed. */
if (pixels == 0) {
return;
}
/* if we got thus far, the containers must have valid percentages. */
assert(first->percent > 0.0);
assert(second->percent > 0.0);

View File

@ -115,7 +115,7 @@ void restore_connect(void) {
#ifdef I3_ASAN_ENABLED
__lsan_do_leak_check();
#endif
errx(EXIT_FAILURE, "Cannot open display\n");
errx(EXIT_FAILURE, "Cannot open display");
}
xcb_watcher = scalloc(1, sizeof(struct ev_io));
@ -180,8 +180,8 @@ static void update_placeholder_contents(placeholder_state *state) {
int y = (state->rect.height / 2) - (config.font.height / 2);
draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
i3string_free(line);
xcb_flush(conn);
xcb_aux_sync(conn);
xcb_flush(restore_conn);
xcb_aux_sync(restore_conn);
}
static void open_placeholder_window(Con *con) {
@ -221,7 +221,7 @@ static void open_placeholder_window(Con *con) {
state->con = con;
state->rect = con->rect;
draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height);
draw_util_surface_init(restore_conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height);
update_placeholder_contents(state);
TAILQ_INSERT_TAIL(&state_head, state, state);

View File

@ -196,7 +196,7 @@ bool scratchpad_show(Con *con) {
Con *output = con_get_output(con);
con->rect.width = output->rect.width * 0.5;
con->rect.height = output->rect.height * 0.75;
floating_check_size(con);
floating_check_size(con, false);
floating_center(con, con_get_workspace(con)->rect);
}

View File

@ -413,8 +413,7 @@ int sd_booted(void) {
struct stat a, b;
/* We simply test whether the systemd cgroup hierarchy is
* mounted */
/* We simply test whether the systemd cgroup hierarchy is mounted */
if (lstat("/sys/fs/cgroup", &a) < 0)
return 0;

View File

@ -190,6 +190,7 @@ void start_application(const char *command, bool no_startup_id) {
/* Setup the environment variable(s) */
if (!no_startup_id)
sn_launcher_context_setup_child_process(context);
setenv("I3SOCK", current_socketpath, 1);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
/* not reached */

View File

@ -248,6 +248,11 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
* mapped. See https://bugs.i3wm.org/1617 */
xcb_change_save_set(conn, XCB_SET_MODE_DELETE, con->window->id);
/* Stop receiving ShapeNotify events. */
if (shape_supported) {
xcb_shape_select_input(conn, con->window->id, false);
}
/* Ignore X11 errors for the ReparentWindow request.
* X11 Errors are returned when the window was already destroyed */
add_ignore_event(cookie.sequence, 0);
@ -295,10 +300,8 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
x_con_kill(con);
if (ws == con) {
DLOG("Closing a workspace container, updating EWMH atoms\n");
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_wm_desktop();
DLOG("Closing workspace container %s, updating EWMH atoms\n", ws->name);
ewmh_update_desktop_properties();
}
con_free(con);
@ -453,7 +456,7 @@ void tree_render(void) {
mark_unmapped(croot);
croot->mapped = true;
render_con(croot, false);
render_con(croot);
x_push_changes(croot);
DLOG("-- END RENDERING --\n");

View File

@ -287,7 +287,7 @@ void i3_restart(bool forget_layout) {
restore_geometry();
ipc_shutdown(SHUTDOWN_REASON_RESTART);
ipc_shutdown(SHUTDOWN_REASON_RESTART, -1);
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or add it */
@ -465,7 +465,7 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
* if the number could be parsed.
*/
bool parse_long(const char *str, long *out, int base) {
char *end;
char *end = NULL;
long result = strtol(str, &end, base);
if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) {
*out = result;

View File

@ -51,14 +51,10 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef
LOG("WM_CLASS changed to %s (instance), %s (class)\n",
win->class_instance, win->class_class);
if (before_mgmt) {
free(prop);
return;
}
if (!before_mgmt) {
run_assignments(win);
free(prop);
}
}
/*
@ -92,14 +88,10 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo
win->uses_net_wm_name = true;
if (before_mgmt) {
free(prop);
return;
}
if (!before_mgmt) {
run_assignments(win);
free(prop);
}
}
/*
@ -141,14 +133,10 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo
win->name_x_changed = true;
if (before_mgmt) {
free(prop);
return;
}
if (!before_mgmt) {
run_assignments(win);
free(prop);
}
}
/*
@ -244,14 +232,10 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
win->role = new_role;
LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
if (before_mgmt) {
free(prop);
return;
}
if (!before_mgmt) {
run_assignments(win);
free(prop);
}
}
/*
@ -272,6 +256,127 @@ void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) {
run_assignments(window);
}
/*
* Updates the WM_NORMAL_HINTS
*
*/
bool window_update_normal_hints(i3Window *win, xcb_get_property_reply_t *reply, xcb_get_geometry_reply_t *geom) {
bool changed = false;
xcb_size_hints_t size_hints;
/* If the hints were already in this event, use them, if not, request them */
bool success;
if (reply != NULL) {
success = xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
} else {
success = xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, win->id), &size_hints, NULL);
}
if (!success) {
DLOG("Could not get WM_NORMAL_HINTS\n");
return false;
}
#define ASSIGN_IF_CHANGED(original, new) \
do { \
if (original != new) { \
original = new; \
changed = true; \
} \
} while (0)
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
ASSIGN_IF_CHANGED(win->min_width, size_hints.min_width);
ASSIGN_IF_CHANGED(win->min_height, size_hints.min_height);
}
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
int max_width = max(0, size_hints.max_width);
int max_height = max(0, size_hints.max_height);
ASSIGN_IF_CHANGED(win->max_width, max_width);
ASSIGN_IF_CHANGED(win->max_height, max_height);
} else {
DLOG("Clearing maximum size \n");
ASSIGN_IF_CHANGED(win->max_width, 0);
ASSIGN_IF_CHANGED(win->max_height, 0);
}
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
DLOG("Size increments: %d (width) x %d (height)\n", size_hints.width_inc, size_hints.height_inc);
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
ASSIGN_IF_CHANGED(win->width_increment, size_hints.width_inc);
} else {
ASSIGN_IF_CHANGED(win->width_increment, 0);
}
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
ASSIGN_IF_CHANGED(win->height_increment, size_hints.height_inc);
} else {
ASSIGN_IF_CHANGED(win->height_increment, 0);
}
} else {
DLOG("Clearing size increments\n");
ASSIGN_IF_CHANGED(win->width_increment, 0);
ASSIGN_IF_CHANGED(win->height_increment, 0);
}
/* The base width / height is the desired size of the window. */
if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE &&
(win->base_width >= 0) && (win->base_height >= 0)) {
DLOG("Base size: %d (width) x %d (height)\n", size_hints.base_width, size_hints.base_height);
ASSIGN_IF_CHANGED(win->base_width, size_hints.base_width);
ASSIGN_IF_CHANGED(win->base_height, size_hints.base_height);
} else {
DLOG("Clearing base size\n");
ASSIGN_IF_CHANGED(win->base_width, 0);
ASSIGN_IF_CHANGED(win->base_height, 0);
}
if (geom != NULL &&
(size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
(size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
DLOG("Setting geometry x=%d y=%d w=%d h=%d\n", size_hints.x, size_hints.y, size_hints.width, size_hints.height);
geom->x = size_hints.x;
geom->y = size_hints.y;
geom->width = size_hints.width;
geom->height = size_hints.height;
}
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT &&
(size_hints.min_aspect_num >= 0) && (size_hints.min_aspect_den > 0) &&
(size_hints.max_aspect_num >= 0) && (size_hints.max_aspect_den > 0)) {
/* Convert numerator/denominator to a double */
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
if (fabs(win->min_aspect_ratio - min_aspect) > DBL_EPSILON) {
win->min_aspect_ratio = min_aspect;
changed = true;
}
if (fabs(win->max_aspect_ratio - max_aspect) > DBL_EPSILON) {
win->max_aspect_ratio = max_aspect;
changed = true;
}
} else {
DLOG("Clearing aspect ratios\n");
ASSIGN_IF_CHANGED(win->min_aspect_ratio, 0.0);
ASSIGN_IF_CHANGED(win->max_aspect_ratio, 0.0);
}
return changed;
}
/*
* Updates the WM_HINTS (we only care about the input focus handling part).
*
@ -318,7 +423,7 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
/* This implementation simply mirrors Gnome's Metacity. Official
/* This implementation simply mirrors Gnome's Metacity. Official
* documentation of this hint is nowhere to be found.
* For more information see:
* https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html

View File

@ -11,9 +11,12 @@
#include "all.h"
#include "yajl_utils.h"
/* Stores a copy of the name of the last used workspace for the workspace
* back-and-forth switching. */
static char *previous_workspace_name = NULL;
/*
* Stores a copy of the name of the last used workspace for the workspace
* back-and-forth switching.
*
*/
char *previous_workspace_name = NULL;
/* NULL-terminated list of workspace names (in order) extracted from
* keybindings. */
@ -158,10 +161,7 @@ Con *workspace_get(const char *num, bool *created) {
con_attach(workspace, content, false);
ipc_send_workspace_event("init", workspace, NULL);
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_wm_desktop();
ewmh_update_desktop_properties();
if (created != NULL)
*created = true;
} else if (created != NULL) {
@ -286,6 +286,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
ws->workspace_layout = config.default_layout;
_workspace_apply_default_orientation(ws);
ipc_send_workspace_event("init", ws, NULL);
return ws;
}
@ -516,10 +517,7 @@ void workspace_show(Con *workspace) {
old_focus = NULL;
}
ewmh_update_number_of_desktops();
ewmh_update_desktop_names();
ewmh_update_desktop_viewport();
ewmh_update_wm_desktop();
ewmh_update_desktop_properties();
}
}
@ -957,26 +955,29 @@ 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, Output *output) {
LOG("Trying to move workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output));
void workspace_move_to_output(Con *ws, Output *output) {
DLOG("Moving workspace %p / %s to output %p / \"%s\".\n", ws, ws->name, output, output_primary_name(output));
Output *current_output = get_output_for_con(ws);
if (current_output == NULL) {
ELOG("Cannot get current output. This is a bug in i3.\n");
return false;
Con *content = output_get_content(output->con);
DLOG("got output %p with content %p\n", output, content);
if (ws->parent == content) {
DLOG("Nothing to do, workspace already there\n");
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->focus_head));
LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
if (previously_visible_ws) {
DLOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
} else {
DLOG("No previously visible workspace on output.\n");
}
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);
DLOG("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;
@ -991,19 +992,18 @@ bool workspace_move_to_output(Con *ws, Output *output) {
}
/* so create the workspace referenced to by this assignment */
LOG("Creating workspace from assignment %s.\n", assignment->name);
DLOG("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)
/* if we couldn't create the workspace using an assignment, create it on
* the output. Workspace init IPC events are sent either by
* workspace_get or create_workspace_on_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");
@ -1011,18 +1011,19 @@ bool workspace_move_to_output(Con *ws, Output *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. */
/* 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);
DLOG("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)
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) {
@ -1030,21 +1031,24 @@ bool workspace_move_to_output(Con *ws, Output *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. */
if (!previously_visible_ws) {
return;
}
/* 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)
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. */
* 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;
}

254
src/x.c
View File

@ -51,6 +51,11 @@ typedef struct con_state {
bool need_reparent;
xcb_window_t old_frame;
/* The container was child of floating container during the previous call of
* x_push_node(). This is used to remove the shape when the container is no
* longer floating. */
bool was_floating;
Rect rect;
Rect window_rect;
@ -94,7 +99,7 @@ static con_state *state_for_frame(xcb_window_t window) {
return state;
/* TODO: better error handling? */
ELOG("No state found\n");
ELOG("No state found for window 0x%08x\n", window);
assert(false);
return NULL;
}
@ -263,6 +268,7 @@ static void _x_con_kill(Con *con) {
draw_util_surface_free(conn, &(con->frame));
draw_util_surface_free(conn, &(con->frame_buffer));
xcb_free_pixmap(conn, con->frame_buffer.id);
con->frame_buffer.id = XCB_NONE;
state = state_for_frame(con->frame.id);
CIRCLEQ_REMOVE(&state_head, state, state);
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
@ -271,7 +277,12 @@ static void _x_con_kill(Con *con) {
free(state);
/* Invalidate focused_id to correctly focus new windows with the same ID */
focused_id = last_focused = XCB_NONE;
if (con->frame.id == focused_id) {
focused_id = XCB_NONE;
}
if (con->frame.id == last_focused) {
last_focused = XCB_NONE;
}
}
/*
@ -355,20 +366,22 @@ static void x_draw_title_border(Con *con, struct deco_render_params *p) {
assert(con->parent != NULL);
Rect *dr = &(con->deco_rect);
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_r = borders_to_hide & ADJ_RIGHT_SCREEN_EDGE ? 0 : con->current_border_width;
if (con->parent->layout == L_TABBED ||
(con->parent->layout == L_STACKED && TAILQ_NEXT(con, nodes) != NULL)) {
deco_diff_l = 0;
deco_diff_r = 0;
}
/* Left */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x, dr->y, 1, dr->height);
/* Right */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x + dr->width - 1, dr->y, 1, dr->height);
/* Top */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x, dr->y, dr->width, 1);
/* Bottom */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1);
dr->x, dr->y + dr->height - 1, dr->width, 1);
}
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) {
@ -390,22 +403,62 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
dr->height);
}
/* Draw a 1px separator line before and after every tab, so that tabs can
* be easily distinguished. */
if (con->parent->layout == L_TABBED) {
/* Left side */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x, dr->y, 1, dr->height);
/* Right side */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
dr->x + dr->width - 1, dr->y, 1, dr->height);
}
/* Redraw the border. */
x_draw_title_border(con, p);
}
/*
* Get rectangles representing the border around the child window. Some borders
* are adjacent to the screen-edge and thus not returned. Return value is the
* number of rectangles.
*
*/
static size_t x_get_border_rectangles(Con *con, xcb_rectangle_t rectangles[4]) {
size_t count = 0;
int border_style = con_border_style(con);
if (border_style != BS_NONE && con_is_leaf(con)) {
adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
Rect br = con_border_style_rect(con);
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
rectangles[count++] = (xcb_rectangle_t){
.x = 0,
.y = 0,
.width = br.x,
.height = con->rect.height,
};
}
if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
rectangles[count++] = (xcb_rectangle_t){
.x = con->rect.width + (br.width + br.x),
.y = 0,
.width = -(br.width + br.x),
.height = con->rect.height,
};
}
if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
rectangles[count++] = (xcb_rectangle_t){
.x = br.x,
.y = con->rect.height + (br.height + br.y),
.width = con->rect.width + br.width,
.height = -(br.height + br.y),
};
}
/* pixel border have an additional line at the top */
if (border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
rectangles[count++] = (xcb_rectangle_t){
.x = br.x,
.y = 0,
.width = con->rect.width + br.width,
.height = br.y,
};
}
}
return count;
}
/*
* Draws the decoration of the given container onto its parent.
*
@ -507,37 +560,24 @@ void x_draw_decoration(Con *con) {
/* 3: draw a rectangle in border color around the client */
if (p->border_style != BS_NONE && p->con_is_leaf) {
/* We might hide some borders adjacent to the screen-edge */
adjacent_t borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
Rect br = con_border_style_rect(con);
/* These rectangles represent the border around the child window
* (left, bottom and right part). We dont just fill the whole
* rectangle because some children are not freely resizable and we want
* their background color to "shine through". */
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
}
if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
draw_util_rectangle(&(con->frame_buffer),
p->color->child_border, r->width + (br.width + br.x), 0,
-(br.width + br.x), r->height);
}
if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
draw_util_rectangle(&(con->frame_buffer),
p->color->child_border, br.x, r->height + (br.height + br.y),
r->width + br.width, -(br.height + br.y));
}
/* pixel border needs an additional line at the top */
if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
draw_util_rectangle(&(con->frame_buffer),
p->color->child_border, br.x, 0, r->width + br.width, br.y);
/* Fill the border. We dont just fill the whole rectangle because some
* children are not freely resizable and we want their background color
* to "shine through". */
xcb_rectangle_t rectangles[4];
size_t rectangles_count = x_get_border_rectangles(con, rectangles);
for (size_t i = 0; i < rectangles_count; i++) {
draw_util_rectangle(&(con->frame_buffer), p->color->child_border,
rectangles[i].x,
rectangles[i].y,
rectangles[i].width,
rectangles[i].height);
}
/* Highlight the side of the border at which the next window will be
* opened if we are rendering a single window within a split container
* (which is undistinguishable from a single window outside a split
* container otherwise. */
Rect br = con_border_style_rect(con);
if (TAILQ_NEXT(con, nodes) == NULL &&
TAILQ_PREV(con, nodes_head, nodes) == NULL &&
con->parent->type != CT_FLOATING_CON) {
@ -573,7 +613,7 @@ void x_draw_decoration(Con *con) {
draw_util_rectangle(&(parent->frame_buffer), p->color->background,
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
/* 5: draw two unconnected horizontal lines in border color */
/* 5: draw title border */
x_draw_title_border(con, p);
/* 6: draw the title */
@ -730,6 +770,71 @@ static void set_hidden_state(Con *con) {
state->is_hidden = should_be_hidden;
}
/*
* Set the container frame shape as the union of the window shape and the
* shape of the frame borders.
*/
static void x_shape_frame(Con *con, xcb_shape_sk_t shape_kind) {
assert(con->window);
xcb_shape_combine(conn, XCB_SHAPE_SO_SET, shape_kind, shape_kind,
con->frame.id,
con->window_rect.x + con->border_width,
con->window_rect.y + con->border_width,
con->window->id);
xcb_rectangle_t rectangles[4];
size_t rectangles_count = x_get_border_rectangles(con, rectangles);
if (rectangles_count) {
xcb_shape_rectangles(conn, XCB_SHAPE_SO_UNION, shape_kind,
XCB_CLIP_ORDERING_UNSORTED, con->frame.id,
0, 0, rectangles_count, rectangles);
}
}
/*
* Reset the container frame shape.
*/
static void x_unshape_frame(Con *con, xcb_shape_sk_t shape_kind) {
assert(con->window);
xcb_shape_mask(conn, XCB_SHAPE_SO_SET, shape_kind, con->frame.id, 0, 0, XCB_PIXMAP_NONE);
}
/*
* Shape or unshape container frame based on the con state.
*/
static void set_shape_state(Con *con, bool need_reshape) {
if (!shape_supported || con->window == NULL) {
return;
}
struct con_state *state;
if ((state = state_for_frame(con->frame.id)) == NULL) {
ELOG("window state for con %p not found\n", con);
return;
}
if (need_reshape && con_is_floating(con)) {
/* We need to reshape the window frame only if it already has shape. */
if (con->window->shaped) {
x_shape_frame(con, XCB_SHAPE_SK_BOUNDING);
}
if (con->window->input_shaped) {
x_shape_frame(con, XCB_SHAPE_SK_INPUT);
}
}
if (state->was_floating && !con_is_floating(con)) {
/* Remove the shape when container is no longer floating. */
if (con->window->shaped) {
x_unshape_frame(con, XCB_SHAPE_SK_BOUNDING);
}
if (con->window->input_shaped) {
x_unshape_frame(con, XCB_SHAPE_SK_INPUT);
}
}
}
/*
* This function pushes the properties of each node of the layout tree to
* X11 if they have changed (like the map state, position of the window, ).
@ -768,6 +873,8 @@ void x_push_node(Con *con) {
con->mapped = false;
}
bool need_reshape = false;
/* reparent the child window (when the window was moved due to a sticky
* container) */
if (state->need_reparent && con->window != NULL) {
@ -793,8 +900,19 @@ void x_push_node(Con *con) {
con->ignore_unmap++;
DLOG("ignore_unmap for reparenting of con %p (win 0x%08x) is now %d\n",
con, con->window->id, con->ignore_unmap);
need_reshape = true;
}
/* We need to update shape when window frame dimensions is updated. */
need_reshape |= state->rect.width != rect.width ||
state->rect.height != rect.height ||
state->window_rect.width != con->window_rect.width ||
state->window_rect.height != con->window_rect.height;
/* We need to set shape when container becomes floating. */
need_reshape |= con_is_floating(con) && !state->was_floating;
/* The pixmap of a borderless leaf container will not be used except
* for the titlebar in a stack or tabs (issue #1013). */
bool is_pixmap_needed = (con->border_style != BS_NONE ||
@ -898,6 +1016,8 @@ void x_push_node(Con *con) {
fake_notify = true;
}
set_shape_state(con, need_reshape);
/* Map if map state changed, also ensure that the child window
* is changed if we are mapped and there is a new, unmapped child window.
* Unmaps are handled in x_push_node_unmaps(). */
@ -941,6 +1061,7 @@ void x_push_node(Con *con) {
}
state->unmap_now = (state->mapped != con->mapped) && !con->mapped;
state->was_floating = con_is_floating(con);
if (fake_notify) {
DLOG("Sending fake configure notify\n");
@ -1205,6 +1326,7 @@ void x_push_changes(Con *con) {
change_ewmh_focus(XCB_WINDOW_NONE, last_focused);
focused_id = ewmh_window;
last_focused = XCB_NONE;
}
xcb_flush(conn);
@ -1325,3 +1447,37 @@ void x_mask_event_mask(uint32_t mask) {
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
}
}
/*
* Enables or disables nonrectangular shape of the container frame.
*/
void x_set_shape(Con *con, xcb_shape_sk_t kind, bool enable) {
struct con_state *state;
if ((state = state_for_frame(con->frame.id)) == NULL) {
ELOG("window state for con %p not found\n", con);
return;
}
switch (kind) {
case XCB_SHAPE_SK_BOUNDING:
con->window->shaped = enable;
break;
case XCB_SHAPE_SK_INPUT:
con->window->input_shaped = enable;
break;
default:
ELOG("Received unknown shape event kind for con %p. This is a bug.\n",
con);
return;
}
if (con_is_floating(con)) {
if (enable) {
x_shape_frame(con, kind);
} else {
x_unshape_frame(con, kind);
}
xcb_flush(conn);
}
}

View File

@ -71,7 +71,7 @@ static void query_screens(xcb_connection_t *conn) {
else
TAILQ_INSERT_TAIL(&outputs, s, outputs);
output_init_con(s);
init_ws_for_output(s, output_get_content(s->con));
init_ws_for_output(s);
num_screens++;
}
@ -98,7 +98,7 @@ static void use_root_output(xcb_connection_t *conn) {
s->active = true;
TAILQ_INSERT_TAIL(&outputs, s, outputs);
output_init_con(s);
init_ws_for_output(s, output_get_content(s->con));
init_ws_for_output(s);
}
/*

View File

@ -402,7 +402,7 @@ int main(int argc, char *argv[]) {
}
if (optind >= argc) {
errx(EXIT_FAILURE, "syntax: %s [options] <command>\n", argv[0]);
errx(EXIT_FAILURE, "syntax: %s [options] <command>", argv[0]);
}
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);

View File

@ -135,7 +135,7 @@ sub activate_i3 {
# We overwrite LISTEN_PID with the correct process ID to make
# socket activation work (LISTEN_PID has to match getpid(),
# otherwise the LISTEN_FDS will be treated as a left-over).
$cmd = qq|strace -fF -s2048 -v -o "$out" -- | .
$cmd = qq|strace -fvy -s2048 -o "$out" -- | .
'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
}

View File

@ -53,6 +53,7 @@ our @EXPORT = qw(
events_for
listen_for_binding
is_net_wm_state_focused
cmp_tree
);
=head1 NAME
@ -1084,6 +1085,229 @@ sub is_net_wm_state_focused {
return 0;
}
=head2 cmp_tree([ $args ])
Compares the tree layout before and after an operation inside a subtest.
The following arguments can be passed:
=over 4
=item layout_before
Required argument. The initial layout to be created. For example,
'H[ V[ a* S[ b c ] d ] e ]' or 'V[a b] T[c d*]'.
The layout will be converted to a JSON file which will be passed to i3's
append_layout command.
The syntax's rules, assertions and limitations are:
=over 8
=item 1.
Upper case letters H, V, S, T mean horizontal, vertical, stacked and tabbed
layout respectively. They must be followed by an opening square bracket and must
be closed with a closing square bracket.
Each of the non-leaf containers is marked with their corresponding letter
followed by a number indicating the position of the container relative to other
containers of the same type. For example, 'H[V[xxx] V[xxx] H[xxx]]' will mark
the non-leaf containers as H1, V1, V2, H2.
=item 2.
Spaces are ignored.
=item 3.
Other alphanumeric characters mean a new window which uses the provided
character for its class and name. Eg 'H[a b]' will open windows with classes 'a'
and 'b' inside a horizontal split. Windows use a single character for their
class, eg 'H[xxx]' will open 3 windows with class 'x'.
=item 4.
Asterisks after a window mean that the window must be focused after the layout
is loaded. Currently, focusing non-leaf containers must be done manually, in the
callback (C<cb>) function.
=back
=item cb
Subroutine to be called after the layout provided by C<layout_before> is created
but before the resulting layout (C<layout_after>) is checked.
=item layout_after
Required argument. The final layout in which the tree is expected to be after
the callback is called. Uses the same syntax with C<layout_before>.
For non-leaf containers, their layout (horizontal, vertical, stacked, tabbed)
is compared with the corresponding letter (H, V, S, T).
For leaf containers, their name is compared with the provided alphanumeric.
=item ws
The workspace in which the layout will be created. Will switch focus to it. If
not provided, a new one is created.
=item msg
Message to prepend to the subtest's name. If not empty, it will be followed by ': '.
=item dont_kill
By default, all windows are killed before the C<layout_before> layout is loaded.
Set to 1 to avoid this.
=back
=cut
sub cmp_tree {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my %args = @_;
my $ws = $args{ws};
if (defined($ws)) {
cmd "workspace $ws";
} else {
$ws = fresh_workspace;
}
my $msg = '';
if ($args{msg}) {
$msg = $args{msg} . ': ';
}
die unless $args{layout_before};
die unless $args{layout_after};
kill_all_windows unless $args{dont_kill};
my @windows = create_layout($args{layout_before});
Test::More::subtest $msg . $args{layout_before} . ' -> ' . $args{layout_after} => sub {
$args{cb}->(\@windows) if $args{cb};
verify_layout($args{layout_after}, $ws);
};
return @windows;
}
sub create_layout {
my $layout = shift;
my $focus;
my @windows = ();
my $r = '';
my $depth = 0;
my %layout_counts = (H => 0, V => 0, S => 0, T => 0);
foreach my $char (split('', $layout)) {
if ($char eq 'H') {
$r = $r . '{"layout": "splith",';
$r = $r . '"marks": ["H' . ++$layout_counts{H} . '"],';
} elsif ($char eq 'V') {
$r = $r . '{"layout": "splitv",';
$r = $r . '"marks": ["V' . ++$layout_counts{V} . '"],';
} elsif ($char eq 'S') {
$r = $r . '{"layout": "stacked",';
$r = $r . '"marks": ["S' . ++$layout_counts{S} . '"],';
} elsif ($char eq 'T') {
$r = $r . '{"layout": "tabbed",';
$r = $r . '"marks": ["T' . ++$layout_counts{T} . '"],';
} elsif ($char eq '[') {
$depth++;
$r = $r . '"nodes": [';
} elsif ($char eq ']') {
# End of nodes array: delete trailing comma.
chop $r;
# When we are at depth 0 we need to split using newlines, making
# multiple "JSON texts".
$depth--;
$r = $r . ']}' . ($depth == 0 ? "\n" : ',');
} elsif ($char eq ' ') {
} elsif ($char eq '*') {
$focus = $windows[$#windows];
} elsif ($char =~ /[[:alnum:]]/) {
push @windows, $char;
$r = $r . '{"swallows": [{';
$r = $r . '"class": "^' . "$char" . '$"';
$r = $r . '}]},';
} else {
die "Could not understand $char";
}
}
die "Invalid layout, depth is $depth > 0" unless $depth == 0;
Test::More::diag($r);
my ($fh, $tmpfile) = tempfile("layout-XXXXXX", UNLINK => 1);
print $fh "$r\n";
close($fh);
my $return = cmd "append_layout $tmpfile";
die 'Could not parse layout json file' unless $return->[0]->{success};
my @result_windows;
push @result_windows, open_window(wm_class => "$_", name => "$_") foreach @windows;
cmd '[class=' . $focus . '] focus' if $focus;
return @result_windows;
}
sub verify_layout {
my ($layout, $ws) = @_;
my $nodes = get_ws_content($ws);
my %counters;
my $depth = 0;
my $node;
foreach my $char (split('', $layout)) {
my $node_name;
my $node_layout;
if ($char eq 'H') {
$node_layout = 'splith';
} elsif ($char eq 'V') {
$node_layout = 'splitv';
} elsif ($char eq 'S') {
$node_layout = 'stacked';
} elsif ($char eq 'T') {
$node_layout = 'tabbed';
} elsif ($char eq '[') {
$depth++;
delete $counters{$depth};
} elsif ($char eq ']') {
$depth--;
} elsif ($char eq ' ') {
} elsif ($char eq '*') {
$tester->is_eq($node->{focused}, 1, 'Correct node focused');
} elsif ($char =~ /[[:alnum:]]/) {
$node_name = $char;
} else {
die "Could not understand $char";
}
if ($node_layout || $node_name) {
if (exists($counters{$depth})) {
$counters{$depth} = $counters{$depth} + 1;
} else {
$counters{$depth} = 0;
}
$node = $nodes->[$counters{0}];
for my $i (1 .. $depth) {
$node = $node->{nodes}->[$counters{$i}];
}
if ($node_layout) {
$tester->is_eq($node->{layout}, $node_layout, "Layouts match in depth $depth, node number " . $counters{$depth});
} else {
$tester->is_eq($node->{name}, $node_name, "Names match in depth $depth, node number " . $counters{$depth});
}
}
}
}
=head1 AUTHOR

View File

@ -16,31 +16,97 @@
#
# Checks if size hints are interpreted correctly.
#
use i3test;
use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
my $tmp = fresh_workspace;
default_floating_border none
floating_minimum_size -1 x -1
floating_maximum_size -1 x -1
EOT
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
sub open_with_aspect {
my ($min_num, $min_den, $max_num, $max_den) = @_;
open_floating_window(
rect => [0, 0, 100, 100],
before_map => sub {
my ($window) = @_;
my $aspect = X11::XCB::Sizehints::Aspect->new;
$aspect->min_num($min_num);
$aspect->min_den($min_den);
$aspect->max_num($max_num);
$aspect->max_den($max_den);
$window->hints->aspect($aspect);
});
}
my $win = open_window({ dont_map => 1 });
# XXX: we should check screen size. in screens with an AR of 2.0,
# this is not a good idea.
my $aspect = X11::XCB::Sizehints::Aspect->new;
$aspect->min_num(600);
$aspect->min_den(300);
$aspect->max_num(600);
$aspect->max_den(300);
$win->_create;
$win->map;
wait_for_map $win;
$win->hints->aspect($aspect);
$x->flush;
################################################################################
# Test aspect ratio set exactly to 2.0
################################################################################
sync_with_i3;
fresh_workspace;
my $win = open_with_aspect(600, 300, 600, 300);
my $rect = $win->rect;
my $ar = $rect->width / $rect->height;
diag("Aspect ratio = $ar");
ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
cmp_float($ar, 2, 'Window set to floating with aspect ratio 2.0');
cmd 'resize set 100';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
cmd 'resize set 400 100';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
# Also check that it is possible to resize by height only
cmd 'resize set height 400';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
is($rect->height, 400, 'Window height is 400px');
cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
cmd 'resize grow height 10';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
is($rect->height, 410, 'Window grew by 10px');
cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
################################################################################
# Test aspect ratio between 0.5 and 2.0
################################################################################
fresh_workspace;
$win = open_with_aspect(1, 2, 2, 1);
$rect = $win->rect;
$ar = $rect->width / $rect->height;
cmp_float($ar, 1, 'Window set to floating with aspect ratio 1.0');
cmd 'resize set 200';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
is($rect->width, 200, 'Window width is 200px');
is($rect->height, 100, 'Window height stayed 100px');
cmp_float($ar, 2, 'Window resized, aspect ratio changed to 2.0');
cmd 'resize set 100 200';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
is($rect->width, 100, 'Window width is 100px');
is($rect->height, 200, 'Window height is 200px');
cmp_float($ar, 0.5, 'Window resized, aspect ratio changed to 0.5');
cmd 'resize set 500';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
cmp_float($ar, 2, 'Window resized, aspect ratio changed to maximum 2.0');
cmd 'resize set 100 400';
$rect = $win->rect;
$ar = $rect->width / $rect->height;
cmp_float($ar, 0.5, 'Window resized, aspect ratio changed to minimum 0.5');
done_testing;

View File

@ -163,6 +163,14 @@ cmd 'scratchpad show';
cmd 'workspace back_and_forth';
is(focused_ws, '6: baz', 'workspace 6 now focused');
################################################################################
# See if BAF is preserved after restart
################################################################################
cmd 'restart';
cmd 'workspace back_and_forth';
is(focused_ws, '5: foo', 'workspace 5 focused after restart');
exit_gracefully($pid);
done_testing;

View File

@ -400,12 +400,18 @@ $config = <<'EOT';
workspace 3 output VGA-1
workspace "4: output" output VGA-2
workspace bleh output LVDS1/I_1
# See #3646
workspace foo output a b c "a b c"
EOT
$expected = <<'EOT';
cfg_workspace(3, VGA-1)
cfg_workspace(4: output, VGA-2)
cfg_workspace(bleh, LVDS1/I_1)
cfg_workspace(foo, a)
cfg_workspace((null), b)
cfg_workspace((null), c)
cfg_workspace((null), a b c)
EOT
is(parser_calls($config),

View File

@ -31,7 +31,7 @@ run [ 'i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
like($stderr, qr#^i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled\.#,
like($stderr, qr#^i3-dump-log: i3 is running, but SHM logging is not enabled\.#,
'shm logging not enabled');
################################################################################
@ -73,7 +73,7 @@ run [ 'i3-dump-log' ],
'>', \$stdout,
'2>', \$stderr;
like($stderr, qr#^i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled\.#,
like($stderr, qr#^i3-dump-log: i3 is running, but SHM logging is not enabled\.#,
'shm logging not enabled');
done_testing;

View File

@ -14,14 +14,20 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
use i3test;
use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# fake-1 under fake-0 to not interfere with left/right wraping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT
################################
# Window focus event
################################
cmd 'split h';
my $ws = fresh_workspace(output => 0);
my $win0 = open_window;
my $win1 = open_window;
my $win2 = open_window;
@ -44,11 +50,52 @@ sub focus_subtest {
is($events[0]->{container}->{name}, $name, "$name focused");
}
sub kill_subtest {
my ($cmd, $name) = @_;
my $focus = AnyEvent->condvar;
my @events = events_for(
sub { cmd $cmd },
'window');
is(scalar @events, 1, 'Received 1 event');
is($events[0]->{change}, 'close', 'Close event received');
is($events[0]->{container}->{name}, $name, "$name closed");
}
subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name;
subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name;
subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name;
subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name;
subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name;
subtest 'focus left', \&focus_subtest, 'focus left', $win2->name;
subtest 'kill doesn\'t produce focus event', \&kill_subtest, '[id=' . $win1->id . '] kill', $win1->name;
# See issue #3562. We need to switch to an existing workspace on the second
# output to trigger the bug.
cmd 'workspace X';
subtest 'workspace focus', \&focus_subtest, "workspace $ws", $win2->name;
sub scratchpad_subtest {
my ($cmd, $name) = @_;
my $focus = AnyEvent->condvar;
my @events = events_for(
sub { cmd $cmd },
'window');
is(scalar @events, 2, 'Received 2 events');
is($events[0]->{change}, 'move', 'Move event received');
is($events[0]->{container}->{nodes}->[0]->{name}, $name, "$name moved");
is($events[1]->{change}, 'focus', 'Focus event received');
is($events[1]->{container}->{name}, $name, "$name focused");
}
fresh_workspace;
my $win = open_window;
cmd 'move scratchpad';
subtest 'scratchpad', \&scratchpad_subtest, '[id=' . $win->id . '] scratchpad show', $win->name;
done_testing;

View File

@ -59,4 +59,33 @@ EOT
is($ret, 0, "exit code == 0");
is($out, "", 'valid config file');
################################################################################
# 3: test duplicate keybindings
################################################################################
$cfg = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bindsym Shift+a nop 1
bindsym Shift+a nop 2
EOT
($ret, $out) = check_config($cfg);
is($ret, 1, "exit code == 1");
like($out, qr/ERROR: *Duplicate keybinding in config file/, 'duplicate keybindings');
################################################################################
# 4: test no duplicate keybindings
################################################################################
$cfg = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bindsym Shift+a nop 1
EOT
($ret, $out) = check_config($cfg);
is($ret, 0, "exit code == 0");
is($out, "", 'valid config file');
done_testing;

View File

@ -16,20 +16,43 @@
#
# Tests the swap command.
# Ticket: #917
use i3test i3_config => <<EOT;
use i3test i3_autostart => 0;
my $config = <<EOT;
# i3 config file (v4)
font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
for_window[class="mark_A"] mark A
for_window[class="mark_B"] mark B
EOT
my $pid = launch_with_config($config);
my ($ws, $ws1, $ws2, $ws3);
my ($node, $nodes, $expected_focus, $A, $B, $F);
my ($result);
my @fullscreen_permutations = ([], ["A"], ["B"], ["A", "B"]);
my $rect_A = [ 100, 100, 100, 100 ];
my $rect_B = [ 200, 200, 200, 200 ];
my @urgent;
sub fullscreen_windows {
my $ws = shift if @_;
scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)}
}
sub cmp_floating_rect {
my ($window, $rect, $prefix) = @_;
sync_with_i3;
my ($absolute, $top) = $window->rect;
is($absolute->{width}, $rect->[2], "$prefix: width matches");
is($absolute->{height}, $rect->[3], "$prefix: height matches");
is($top->{x}, $rect->[0], "$prefix: x matches");
is($top->{y}, $rect->[1], "$prefix: y matches");
}
###############################################################################
# Invalid con_id should not crash i3
# See issue #2895.
@ -261,7 +284,6 @@ sync_with_i3;
cmd '[con_mark=B] swap container with mark A';
sync_with_i3;
does_i3_live;
$nodes = get_ws_content($ws1);
@ -502,6 +524,142 @@ cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height');
kill_all_windows;
###############################################################################
# Swap floating windows in the same workspace. Check that they exchange rects.
###############################################################################
$ws = fresh_workspace;
$A = open_floating_window(wm_class => 'mark_A', rect => $rect_A);
$B = open_floating_window(wm_class => 'mark_B', rect => $rect_B);
cmd '[con_mark=B] swap container with mark A';
cmp_floating_rect($A, $rect_B, 'A got B\'s rect');
cmp_floating_rect($B, $rect_A, 'B got A\'s rect');
kill_all_windows;
###############################################################################
# Swap a fullscreen floating and a normal floating window.
###############################################################################
$ws1 = fresh_workspace;
$A = open_floating_window(wm_class => 'mark_A', rect => $rect_A, fullscreen => 1);
$ws2 = fresh_workspace;
$B = open_floating_window(wm_class => 'mark_B', rect => $rect_B);
cmd '[con_mark=B] swap container with mark A';
cmp_floating_rect($A, $rect_B, 'A got B\'s rect');
$nodes = get_ws($ws1);
$node = $nodes->{floating_nodes}->[0]->{nodes}->[0];
is($node->{window}, $B->{id}, 'B is on the first workspace');
is($node->{fullscreen_mode}, 1, 'B is now fullscreened');
$nodes = get_ws($ws2);
$node = $nodes->{floating_nodes}->[0]->{nodes}->[0];
is($node->{window}, $A->{id}, 'A is on the second workspace');
is($node->{fullscreen_mode}, 0, 'A is not fullscreened anymore');
kill_all_windows;
###############################################################################
# Swap a floating window which is in a workspace that has another, regular
# window with a regular window in another workspace. A & B focused.
#
# Before:
# +-----------+
# | F |
# | +---+ |
# | | A | |
# | +---+ |
# | |
# +-----------+
#
# +-----------+
# | |
# | |
# | B |
# | |
# | |
# +-----------+
#
# After: Same as above but A <-> B. A & B focused.
###############################################################################
$ws1 = fresh_workspace;
open_window;
$A = open_floating_window(wm_class => 'mark_A', rect => $rect_A);
$ws2 = fresh_workspace;
$B = open_window(wm_class => 'mark_B');
$expected_focus = get_focused($ws2);
cmd '[con_mark=B] swap container with mark A';
$nodes = get_ws($ws1);
$node = $nodes->{floating_nodes}->[0]->{nodes}->[0];
is($node->{window}, $B->{id}, 'B is floating on the first workspace');
is(get_focused($ws1), $expected_focus, 'B is focused');
cmp_floating_rect($B, $rect_A, 'B got A\'s rect');
$nodes = get_ws_content($ws2);
$node = $nodes->[0];
is($node->{window}, $A->{id}, 'A is on the second workspace');
kill_all_windows;
###################################################################
# Test that swapping a floating window maintains the correct
# floating order.
###################################################################
$ws = fresh_workspace;
$A = open_floating_window(wm_class => 'mark_A', rect => $rect_A);
$B = open_window(wm_class => 'mark_B');
$F = open_floating_window;
$expected_focus = get_focused($ws);
cmd '[con_mark=B] swap container with mark A';
$nodes = get_ws($ws);
$node = $nodes->{floating_nodes}->[0]->{nodes}->[0];
is($node->{window}, $B->{id}, 'B is floating, bottom');
cmp_floating_rect($B, $rect_A, 'B got A\'s rect');
$node = $nodes->{floating_nodes}->[1]->{nodes}->[0];
is($node->{window}, $F->{id}, 'F is floating, top');
is(get_focused($ws), $expected_focus, 'F still focused');
$node = $nodes->{nodes}->[0];
is($node->{window}, $A->{id}, 'A is tiling');
kill_all_windows;
###############################################################################
# Swap a sticky, floating container A and a floating fullscreen container B.
# A should remain sticky and floating and should be fullscreened.
###############################################################################
$ws1 = fresh_workspace;
open_window;
$A = open_floating_window(wm_class => 'mark_A', rect => $rect_A);
$expected_focus = get_focused($ws1);
cmd 'sticky enable';
$B = open_floating_window(wm_class => 'mark_B', rect => $rect_B);
cmd 'fullscreen enable';
cmd '[con_mark=B] swap container with mark A';
is(@{get_ws($ws1)->{floating_nodes}}, 2, '2 fullscreen containers on first workspace');
is(get_focused($ws1), $expected_focus, 'A is focused');
$ws2 = fresh_workspace;
cmd 'fullscreen disable';
cmp_floating_rect($A, $rect_B, 'A got B\'s rect');
is(@{get_ws($ws2)->{floating_nodes}}, 1, 'only A in new workspace');
cmd "workspace $ws1"; # TODO: Why does rect check fails without switching workspaces?
cmp_floating_rect($B, $rect_A, 'B got A\'s rect');
kill_all_windows;
###############################################################################
# Swapping containers moves the urgency hint correctly.
###############################################################################
@ -527,6 +685,64 @@ is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent');
kill_all_windows;
###############################################################################
# Swapping an urgent container from another workspace with the focused
# container removes the urgency hint from both the container and the previous
# workspace.
###############################################################################
$ws2 = fresh_workspace;
$B = open_window(wm_class => 'mark_B');
$ws1 = fresh_workspace;
$A = open_window(wm_class => 'mark_A');
$B->add_hint('urgency');
sync_with_i3;
cmd '[con_mark=B] swap container with mark A';
@urgent = grep { $_->{urgent} } @{get_ws_content($ws1)};
is(@urgent, 0, 'B is not marked urgent');
is(get_ws($ws1)->{urgent}, 0, 'the first workspace is not marked urgent');
@urgent = grep { $_->{urgent} } @{get_ws_content($ws2)};
is(@urgent, 0, 'A is not marked urgent');
is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent');
kill_all_windows;
exit_gracefully($pid);
###############################################################################
# Test that swapping with workspace_layout doesn't crash i3.
# See issue #3280.
###############################################################################
$config = <<EOT;
# i3 config file (v4)
font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
workspace_layout stacking
EOT
$pid = launch_with_config($config);
$ws = fresh_workspace;
open_window;
open_window;
$B = open_window;
cmd 'move right';
cmd 'focus left, focus parent, mark a';
cmd 'focus right';
cmd 'swap with mark a';
does_i3_live;
$nodes = get_ws_content($ws);
is($nodes->[0]->{window}, $B->{id}, 'B is on the left');
exit_gracefully($pid);
###############################################################################
done_testing;

View File

@ -210,6 +210,45 @@ cmd '[id=' . $windows[2]->id . '] move to workspace ' . $ws;
cmd '[id=' . $windows[1]->id . '] move to workspace ' . $ws;
confirm_focus('\'move to workspace\' focus order when moving containers from other workspace');
######################################################################
# Swapping sibling containers correctly swaps focus order.
######################################################################
sub swap_with_ids {
my ($idx1, $idx2) = @_;
cmd '[id=' . $windows[$idx1]->id . '] swap container with id ' . $windows[$idx2]->id;
}
$ws = fresh_workspace;
$windows[2] = open_window;
$windows[0] = open_window;
$windows[3] = open_window;
$windows[1] = open_window;
# If one of the swapees is focused we deliberately preserve its focus, switch
# workspaces to move focus away from the windows.
fresh_workspace;
# 2 0 3 1 <- focus order in this direction
swap_with_ids(3, 1);
# 2 0 1 3
swap_with_ids(2, 3);
# 3 0 1 2
swap_with_ids(0, 2);
# 3 2 1 0
# Restore input focus for confirm_focus
cmd "workspace $ws";
# Also confirm swap worked
$ws = get_ws($ws);
for my $i (0 .. 3) {
my $node = $ws->{nodes}->[3 - $i];
is($node->{window}, $windows[$i]->id, "window $i in correct position after swap");
}
confirm_focus('\'swap container with id\' focus order');
######################################################################
# Test focus order with floating and tiling windows.
# See issue: 1975

View File

@ -101,11 +101,19 @@ is_deeply(\@actual_names, \@expected_names);
# Kill first window to close a workspace.
cmd '[id="' . $second->id . '"] kill';
is(get_current_desktop, 2, '_NET_CURRENT_DESKTOP should be updated');
is(get_current_desktop, 1, '_NET_CURRENT_DESKTOP should be updated');
is(get_num_of_desktops, 2, '_NET_NUMBER_OF_DESKTOPS should be updated');
my @actual_names = get_desktop_names;
my @expected_names = ('0', '2');
is_deeply(\@actual_names, \@expected_names, '_NET_DESKTOP_NAMES should be updated');
# Rename workspace to reorder them.
cmd 'rename workspace 0 to 5';
is(get_current_desktop, 0, '_NET_CURRENT_DESKTOP should be updated');
is(get_num_of_desktops, 2, '_NET_NUMBER_OF_DESKTOPS should remain the same');
my @actual_names = get_desktop_names;
my @expected_names = ('2', '5');
is_deeply(\@actual_names, \@expected_names, '_NET_DESKTOP_NAMES should be updated');
done_testing;

View File

@ -79,7 +79,9 @@ workspace 1 output fake-1 fake-2
workspace 2 output fake-3 fake-4 fake-0 fake-1
workspace 3 output these outputs do not exist but these do: fake-0 fake-3
workspace 4 output whitespace fake-0
workspace special output doesnotexist1 doesnotexist2 doesnotexist3
workspace foo output doesnotexist1 doesnotexist2 doesnotexist3
workspace bar output doesnotexist
workspace bar output fake-0
EOT
$pid = launch_with_config($config);
@ -91,8 +93,11 @@ do_test('4', 'fake-0', 'Excessive whitespace is ok');
do_test('5', 'fake-1', 'Numbered initialization for fake-1');
do_test('6', 'fake-2', 'Numbered initialization for fake-2');
cmd 'focus output fake-0, workspace special';
check_output('special', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output.');
cmd 'focus output fake-0, workspace foo';
check_output('foo', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output');
cmd 'focus output fake-0, workspace bar';
check_output('bar', 'fake-0', 'Second workspace assignment line ignored');
# Moving assigned workspaces.
cmd 'workspace 2, move workspace to output left';

114
testcases/t/301-shape.t Normal file
View File

@ -0,0 +1,114 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://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 shape support.
# Ticket: #2742
use i3test;
use ExtUtils::PkgConfig;
my %sn_config;
BEGIN {
%sn_config = ExtUtils::PkgConfig->find('xcb-shape');
}
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
use Inline C => <<'END_OF_C_CODE';
#include <xcb/shape.h>
static xcb_connection_t *conn;
void init_ctx(void *connptr) {
conn = (xcb_connection_t*)connptr;
}
/*
* Set the shape for the window consisting of the following zones:
*
* +---+---+
* | A | B |
* +---+---+
* | C |
* +-------+
*
* - Zone A is completly opaque.
* - Zone B is clickable through (input shape).
* - Zone C is completly transparent (bounding shape).
*/
void set_shape(long window_id) {
xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };
xcb_shape_rectangles(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING,
XCB_CLIP_ORDERING_UNSORTED, window_id,
0, 0, 1, &bounding_rectangle);
xcb_rectangle_t input_rectangle = { 0, 0, 50, 50 };
xcb_shape_rectangles(conn, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT,
XCB_CLIP_ORDERING_UNSORTED, window_id,
0, 0, 1, &input_rectangle);
xcb_flush(conn);
}
END_OF_C_CODE
init_ctx($x->get_xcb_conn());
my ($ws, $win1, $win1_focus, $win2, $win2_focus);
################################################################################
# Case 1: make floating window, then set shape
################################################################################
$ws = fresh_workspace;
$win1 = open_floating_window(rect => [0, 0, 100, 100], background_color => '#ff0000');
$win1_focus = get_focused($ws);
$win2 = open_floating_window(rect => [0, 0, 100, 100], background_color => '#00ff00');
$win2_focus = get_focused($ws);
set_shape($win2->id);
$win1->warp_pointer(75, 25);
sync_with_i3;
is(get_focused($ws), $win1_focus, 'focus switched to the underlying window');
$win1->warp_pointer(25, 25);
sync_with_i3;
is(get_focused($ws), $win2_focus, 'focus switched to the top window');
kill_all_windows;
################################################################################
# Case 2: set shape first, then make window floating
################################################################################
$ws = fresh_workspace;
$win1 = open_window(rect => [0, 0, 100, 100], background_color => '#ff0000');
$win1_focus = get_focused($ws);
cmd 'floating toggle';
$win2 = open_window(rect => [0, 0, 100, 100], background_color => '#00ff00');
$win2_focus = get_focused($ws);
set_shape($win2->id);
cmd 'floating toggle';
sync_with_i3;
$win1->warp_pointer(75, 25);
sync_with_i3;
is(get_focused($ws), $win1_focus, 'focus switched to the underlying window');
$win1->warp_pointer(25, 25);
sync_with_i3;
is(get_focused($ws), $win2_focus, 'focus switched to the top window');
done_testing;

94
testcases/t/302-tree.t Normal file
View File

@ -0,0 +1,94 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://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)
#
# Contains various tests that use the cmp_tree subroutine.
# Ticket: #3503
use i3test;
sub sanity_check {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($layout, $focus_idx) = @_;
my @windows = cmp_tree(
msg => 'Sanity check',
layout_before => $layout,
layout_after => $layout);
is($x->input_focus, $windows[$focus_idx]->id, 'Correct window focused') if $focus_idx >= 0;
}
sanity_check('H[ V[ a* V[ b c ] d ] e ]', 0);
sanity_check('H[ a b c d* ]', 3);
sanity_check('V[a b] V[c d*]', 3);
sanity_check('T[a b] S[c*]', 2);
cmp_tree(
msg => 'Simple focus test',
layout_before => 'H[a b] V[c* d]',
layout_after => 'H[a* b] V[c d]',
cb => sub {
cmd '[class=a] focus';
});
cmp_tree(
msg => 'Simple move test',
layout_before => 'H[a b] V[c* d]',
layout_after => 'H[a b] V[d c*]',
cb => sub {
cmd 'move down';
});
cmp_tree(
msg => 'Move from horizontal to vertical',
layout_before => 'H[a b] V[c d*]',
layout_after => 'H[b] V[c d a*]',
cb => sub {
cmd '[class=a] focus';
cmd 'move right, move right';
});
cmp_tree(
msg => 'Move unfocused non-leaf container',
layout_before => 'S[a b] V[c d* T[e f g]]',
layout_after => 'S[a T[e f g] b] V[c d*]',
cb => sub {
cmd '[con_mark=T1] move up, move up, move left, move up';
});
cmp_tree(
msg => 'Simple swap test',
layout_before => 'H[a b] V[c d*]',
layout_after => 'H[a d*] V[c b]',
cb => sub {
cmd '[class=b] swap with id ' . $_[0][3]->{id};
});
cmp_tree(
msg => 'Swap non-leaf containers',
layout_before => 'S[a b] V[c d*]',
layout_after => 'V[c d*] S[a b]',
cb => sub {
cmd '[con_mark=S1] swap with mark V1';
});
cmp_tree(
msg => 'Swap nested non-leaf containers',
layout_before => 'S[a b] V[c d* T[e f g]]',
layout_after => 'T[e f g] V[c d* S[a b]]',
cb => sub {
cmd '[con_mark=S1] swap with mark T1';
});
done_testing;

View File

@ -0,0 +1,69 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://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 the workspace init event is correctly sent.
# Ticket: #3631
# Bug still in: 4.16-85-g2d6e09a6
use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# fake-1 under fake-0 to not interfere with left/right wraping
fake-outputs 1024x768+0+0,1024x768+0+1024
workspace X output fake-1
EOT
sub workspace_init_subtest {
my $cmd = shift;
my $num_events = shift;
my @events = events_for(sub { cmd $cmd }, 'workspace');
my @init = grep { $_->{change} eq 'init' } @events;
my $len = scalar @init;
is($len, $num_events, "Received $num_events workspace::init event");
$num_events = $len if $len < $num_events;
for my $idx (0 .. $num_events - 1) {
my $name = shift;
my $output = shift;
is($init[$idx]->{current}->{name}, $name, "workspace name $name matches");
is($init[$idx]->{current}->{output}, $output, "workspace output $output matches");
}
}
subtest 'focus outputs', \&workspace_init_subtest, 'focus output fake-1, focus output fake-0', 0;
subtest 'new workspaces', \&workspace_init_subtest,
'workspace a, workspace b, workspace a, workspace a', 3, 'a',
'fake-0', 'b', 'fake-0', 'a', 'fake-0';
open_window; # Prevent workspace "a" from being deleted.
subtest 'return on existing workspace', \&workspace_init_subtest,
'workspace a, workspace b, workspace a', 1, 'b', 'fake-0';
subtest 'assigned workspace is already open', \&workspace_init_subtest,
'workspace X, workspace b, workspace a', 1, 'b', 'fake-1';
subtest 'assigned workspace was deleted and now is initialized again', \&workspace_init_subtest,
'workspace X, workspace b, workspace a', 2, 'X', 'fake-1', 'b', 'fake-1';
subtest 'move workspace to output', \&workspace_init_subtest,
'move workspace to output fake-1, move workspace to output fake-0', 2, '1', 'fake-0', 'X',
'fake-1';
subtest 'move window to workspace', \&workspace_init_subtest, 'move to workspace b', 1, 'b',
'fake-0';
subtest 'back_and_forth', \&workspace_init_subtest,
'workspace b, workspace back_and_forth, workspace b', 1,
'a', 'fake-0';
subtest 'move window to workspace back_and_forth', \&workspace_init_subtest,
'move window to workspace back_and_forth', 1, 'a', 'fake-0';
done_testing;

View File

@ -0,0 +1,25 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://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)
#
# Verify that restart produces a reply.
# Ticket: #3565
# Bug still in: 4.16-143-gca82f958
use i3test;
my $reply = cmd('restart');
ok($reply->[0]->{success}, 'restart reply received');
done_testing;

View File

@ -86,4 +86,33 @@ is(focused_output, 'fake-1', 'focus on second output');
cmd 'focus output fake-0';
is(focused_output, 'fake-0', 'focus on first output');
################################################################################
# use 'focus output' with command criteria and verify that i3 does not crash
# when they don't match any window
################################################################################
is(focused_output, 'fake-0', 'focus on first output');
cmd '[con_mark=doesnotexist] focus output right';
does_i3_live;
is(focused_output, 'fake-0', 'focus remained on first output');
################################################################################
# use 'focus output' with command criteria and verify that focus gets changed
# appropriately
################################################################################
is(focused_output, 'fake-0', 'focus on first output');
my $window = open_window;
cmd 'focus output right';
is(focused_output, 'fake-1', 'focus on second output');
cmd '[id= . ' . $window->id . '] focus output right';
is(focused_output, 'fake-1', 'focus on second output after command with criteria');
cmd 'focus output right';
is(focused_output, 'fake-0', 'focus on first output after command without criteria');
done_testing;

View File

@ -130,10 +130,16 @@ $tree = i3->get_tree->recv;
is_deeply(\@outputs, [ '__i3', 'default' ], 'outputs are __i3 and default');
SKIP: {
skip 'xrandr --setmonitor failed (xrandr too old?)', 1 unless
system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0;
my @events = events_for(
sub {
skip 'xrandr --setmonitor failed (xrandr too old?)', 1
unless system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0;
},
"workspace");
sync_with_i3;
my @init = grep { $_->{change} eq 'init' } @events;
is(scalar @init, 1, 'Received 1 workspace::init event');
is($init[0]->{current}->{output}, 'up2414q', 'Workspace initialized in up2414q');
$tree = i3->get_tree->recv;
@outputs = map { $_->{name} } @{$tree->{nodes}};

View File

@ -15,7 +15,7 @@
{
"includePattern": "build/deb/ubuntu-amd64/(.*\\.deb)$",
"matrixParams": {
"deb_distribution": "xenial",
"deb_distribution": "bionic",
"deb_component": "main",
"deb_architecture": "amd64"
},
@ -24,7 +24,7 @@
{
"includePattern": "build/deb/ubuntu-i386/(.*\\.deb)$",
"matrixParams": {
"deb_distribution": "xenial",
"deb_distribution": "bionic",
"deb_component": "main",
"deb_architecture": "i386"
},

View File

@ -3,4 +3,4 @@
set -e
set -x
clang-format-4.0 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
clang-format-6.0 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)

View File

@ -15,6 +15,6 @@ docker build --pull --no-cache --rm -t=${BASENAME} -f ${DOCKERFILE} .
# the login+push step when the variable isnt set.
if [ -n "${DOCKER_PASS}" ]
then
docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS}
docker login -u ${DOCKER_USER} -p ${DOCKER_PASS}
docker push ${BASENAME}
fi

View File

@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, lets stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
# clang and clang-format-4.0 (for checking formatting and building with clang),
# clang and clang-format-6.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
RUN linux32 apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
clang clang-format-4.0 \
clang clang-format-6.0 \
lintian && \
rm -rf /var/lib/apt/lists/*

View File

@ -1,7 +1,7 @@
# vim:ft=Dockerfile
# Same as travis-base.Dockerfile, but without the test suite dependencies since
# we only build Debian packages on Ubuntu i386, we dont run the tests.
FROM i386/ubuntu:xenial
FROM i386/ubuntu:bionic
RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
# Paper over occasional network flakiness of some mirrors.
@ -13,21 +13,17 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
# (3608 kB/s)). Hence, lets stick with httpredir.debian.org (default) for now.
# Install mk-build-deps (for installing the i3 build dependencies),
# clang and clang-format-4.0 (for checking formatting and building with clang),
# clang and clang-format-6.0 (for checking formatting and building with clang),
# lintian (for checking spelling errors),
RUN linux32 apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
dpkg-dev devscripts git equivs \
clang clang-format-4.0 \
clang clang-format-6.0 \
lintian && \
rm -rf /var/lib/apt/lists/*
# Install i3 build dependencies.
COPY debian/control /usr/src/i3-debian-packaging/control
RUN echo 'deb http://dl.bintray.com/i3/i3-autobuild-ubuntu xenial main' > /etc/apt/sources.list.d/i3-autobuild.list && \
linux32 apt-get update && \
linux32 apt-get --allow-unauthenticated install i3-autobuild-keyring && \
rm -f /var/lib/apt/lists/dl.bintray.com_i3_i3-autobuild-ubuntu_* && \
linux32 apt-get update && \
RUN linux32 apt-get update && \
DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
rm -rf /var/lib/apt/lists/*

Some files were not shown because too many files have changed in this diff Show More