Merge branch 'next' into master
This commit is contained in:
commit
ea00565ad3
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
4.16.1-non-git
|
||||
4.17-non-git
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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
|
||||
• don’t 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
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
------------------------------------------
|
||||
|
||||
|
|
32
docs/ipc
32
docs/ipc
|
@ -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 i3’s
|
||||
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
|
||||
|
|
|
@ -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, you’d 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.
|
||||
|
|
24
etc/config
24
etc/config
|
@ -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:
|
||||
#######################################################################
|
||||
|
|
|
@ -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 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 Mouse+$mod to drag floating windows to their wanted position
|
||||
floating_modifier $mod
|
||||
|
|
|
@ -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); \
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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); \
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
125
i3bar/src/xcb.c
125
i3bar/src/xcb.c
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -400,28 +400,24 @@ struct tray_output_t {
|
|||
tray_outputs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the configuration file to use (either the one specified by
|
||||
* override_configpath), the user’s one or the system default) and calls
|
||||
* parse_file().
|
||||
*
|
||||
* 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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
------------------------------------------------
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
15
release.sh
15
release.sh
|
@ -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)"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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_layout’s
|
||||
// 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 don’t 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);
|
||||
|
|
|
@ -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
245
src/con.c
|
@ -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);
|
||||
}
|
||||
|
|
87
src/config.c
87
src/config.c
|
@ -39,42 +39,12 @@ void update_barconfig(void) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the configuration file to use (either the one specified by
|
||||
* override_configpath), the user’s one or the system default) and calls
|
||||
* parse_file().
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* don’t 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) {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
119
src/ewmh.c
119
src/ewmh.c
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
115
src/floating.c
115
src/floating.c
|
@ -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 isn’t explicit on whether the aspect ratio hints should be
|
||||
* respected during fullscreen mode. Other WMs such as Openbox don’t 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 GIMP’s 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: don’t 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)
|
||||
|
|
150
src/handlers.c
150
src/handlers.c
|
@ -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 don’t want it,
|
||||
since it’d 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:
|
||||
|
|
56
src/ipc.c
56
src/ipc.c
|
@ -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 don’t 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
61
src/main.c
61
src/main.c
|
@ -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));
|
||||
|
|
59
src/manage.c
59
src/manage.c
|
@ -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 isn’t enough either — it needs the rect. */
|
||||
ws->rect = ws->parent->rect;
|
||||
render_con(ws, true);
|
||||
render_con(ws);
|
||||
/* Disable setting focus, otherwise we’d 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);
|
||||
|
|
10
src/output.c
10
src/output.c
|
@ -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;
|
||||
}
|
||||
|
|
84
src/randr.c
84
src/randr.c
|
@ -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 it’s
|
||||
* invisible, it won’t 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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
67
src/render.c
67
src/render.c
|
@ -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 isn’t explicit on whether the aspect ratio hints should be
|
||||
* respected during fullscreen mode. Other WMs such as Openbox don’t 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
|
||||
* (it’s 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 (it’s 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
33
src/resize.c
33
src/resize.c
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
13
src/tree.c
13
src/tree.c
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
155
src/window.c
155
src/window.c
|
@ -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
|
||||
|
|
|
@ -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
254
src/x.c
|
@ -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 don’t 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 don’t 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 . '"';
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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}};
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -15,6 +15,6 @@ docker build --pull --no-cache --rm -t=${BASENAME} -f ${DOCKERFILE} .
|
|||
# the login+push step when the variable isn’t 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
|
||||
|
|
|
@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
|
|||
# (3608 kB/s)). Hence, let’s 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/*
|
||||
|
||||
|
|
|
@ -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 don’t 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, let’s 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
Loading…
Reference in New Issue