Merge branch 'next'
This commit is contained in:
commit
28939365cb
|
@ -6,6 +6,7 @@ include/all.h.pch
|
|||
*.swp
|
||||
*.gcda
|
||||
*.gcno
|
||||
*.dSYM
|
||||
test.commands_parser
|
||||
test.config_parser
|
||||
testcases/MYMETA.json
|
||||
|
@ -31,3 +32,4 @@ docs/*.html
|
|||
!/docs/refcard.html
|
||||
i3-command-parser.stamp
|
||||
i3-config-parser.stamp
|
||||
.clang_complete
|
||||
|
|
4
DEPENDS
4
DEPENDS
|
@ -10,13 +10,13 @@
|
|||
│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │
|
||||
│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │
|
||||
│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │
|
||||
│ util-cursor³│ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/ │
|
||||
│ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │
|
||||
│ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │
|
||||
│ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │
|
||||
│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │
|
||||
│ Pod::Simple²│ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/
|
||||
│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │
|
||||
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
|
||||
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
|
||||
│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
|
||||
│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification
|
||||
|
@ -26,6 +26,8 @@
|
|||
¹ libsn = libstartup-notification
|
||||
² Pod::Simple is a Perl module required for converting the testsuite
|
||||
documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple
|
||||
³ xcb-util-cursor, to be precise. Might be considered part of xcb-util, or not
|
||||
:-).
|
||||
|
||||
i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any
|
||||
new dependencies.
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.7 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is the i3 v4.7. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
This release features a number of documentation improvements, better error
|
||||
messages in various places, better tray compatibility in i3bar, and a number of
|
||||
bugfixes.
|
||||
|
||||
Relevant from a packaging point of view is that we have switched to the new
|
||||
xcb-util-cursor library to get rid of libXcursor. The last remaining big piece
|
||||
of Xlib code now is XKB, which we may be able to tackle in upcoming releases
|
||||
thanks to the just released libxcb 1.10.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in v4.7 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• docs/userguide: clarify variable parsing
|
||||
• docs/userguide: clarify urgent_workspace
|
||||
• docs/userguide: add proper quoting for rename sample command
|
||||
• docs/userguide: clarify multiple criteria
|
||||
• docs/userguide: userguide: explain the difference between comma and semicolon for command chaining
|
||||
• docs/hacking-howto: update to reflect parser changes
|
||||
• man/i3-dump-log: document -f
|
||||
• switch from libXcursor to xcb-util-cursor
|
||||
• Respect workspace numbers when looking for a free workspace name
|
||||
• Revert "raise fullscreen windows on top of all other X11 windows"
|
||||
• i3bar: Create pixmaps using the real bar height, rather than screen height
|
||||
• Add scratchpad bindings to the default config
|
||||
• Close all children when closing a workspace
|
||||
• i3bar: Add new bar.binding_mode_indicator configuration
|
||||
• Improve error message when $XDG_RUNTIME_DIR is not writable
|
||||
• libi3/font: Draw the text at the expected place
|
||||
• libi3/font: Set DPI for the pango context
|
||||
• Add ability to escape out of a mouse-resize operation
|
||||
• Do not resize/reposition floating containers when moving them to scratchpad
|
||||
• i3-nagbar: Set button inner-width to the width of the label
|
||||
• Assigned windows open urgent when not visible
|
||||
• i3bar: Only configure tray on own outputs
|
||||
• Command 'move <direction>' moves across outputs
|
||||
• i3bar: Handle DestroyNotify events
|
||||
• i3bar: Realign tray clients on map/unmap notify
|
||||
• i3bar: Group child processes for signalling
|
||||
• i3bar: Print error message when status_command fails
|
||||
• Remove references to PATH_MAX macro for GNU/Hurd
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• update root geometry on output changes for “fullscreen global”
|
||||
• don’t flatten tabbed/stacked containers
|
||||
• Fix handling of new windows with WM_STATE_FULLSCREEN
|
||||
• correctly recognize assigned windows as urgent
|
||||
• Fix keyboard and mouse resize in nested containers
|
||||
• Reply to _NET_REQUEST_FRAME_EXTENTS correctly
|
||||
• Fix command parser: resizing tiling windows
|
||||
• Fix output retrieval for floating cons
|
||||
• Use _PATH_BSHELL to ensure using a bourne shell
|
||||
• Instead of crashing, return DRAG_ABORT on UnmapNotify from drag_pointer
|
||||
• Remove-child callback skips output content cons
|
||||
• ignore _NET_ACTIVE_WINDOW for scratchpad windows
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Alexander Neumann, badboy, Baptiste Daroussin, Bas Pape, Deiz, Franck Michea,
|
||||
Jean-Philippe Ouellet, jj, jookia, kaersten, Lancelot SIX, Leo Gaspard,
|
||||
mistnim, Peter Maatman, Quentin Glidic, Sebastian Ullrich, Slava, syl20bnr,
|
||||
Tony Crisci, Trung Ngo, Vivien Didelot, Xarthisius
|
||||
|
||||
I want to specifically thank Tony Crisci for the very valuable help with
|
||||
responding to bugreports in our bugtracker. Thank you!
|
||||
|
||||
-- Michael Stapelberg, 2013-12-22
|
|
@ -2,8 +2,6 @@ UNAME=$(shell uname)
|
|||
DEBUG=1
|
||||
COVERAGE=0
|
||||
INSTALL=install
|
||||
FLEX=flex
|
||||
BISON=bison
|
||||
ifndef PREFIX
|
||||
PREFIX=/usr
|
||||
endif
|
||||
|
@ -111,8 +109,8 @@ X11_CFLAGS := $(call cflags_for_lib, x11)
|
|||
X11_LIBS := $(call ldflags_for_lib, x11,X11)
|
||||
|
||||
# Xcursor
|
||||
XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor)
|
||||
XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor)
|
||||
XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor)
|
||||
XCURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor)
|
||||
|
||||
# yajl
|
||||
YAJL_CFLAGS := $(call cflags_for_lib, yajl)
|
||||
|
@ -144,7 +142,7 @@ PANGO_LIBS := $(call ldflags_for_lib, cairo)
|
|||
PANGO_LIBS += $(call ldflags_for_lib, pangocairo)
|
||||
|
||||
# libi3
|
||||
LIBS = -L$(TOPDIR) -li3
|
||||
LIBS = -L$(TOPDIR) -li3 -lm
|
||||
|
||||
## Platform-specific flags
|
||||
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
i3-wm (4.5.2-1) experimental; urgency=low
|
||||
i3-wm (4.6.1-1) unstable; urgency=low
|
||||
|
||||
* NOT YET RELEASED
|
||||
* NOT YET RELEASED.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 18 Mar 2013 23:01:30 +0100
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 07 Aug 2013 20:53:26 +0200
|
||||
|
||||
i3-wm (4.6-1) unstable; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 07 Aug 2013 20:53:26 +0200
|
||||
|
||||
i3-wm (4.5.1-2) unstable; urgency=low
|
||||
|
||||
* experimental to unstable because i3-wm 4.5.1 was only in experimental due
|
||||
to the freeze.
|
||||
* bump standards-version to 3.9.4 (no changes necessary)
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Tue, 14 May 2013 20:48:16 +0200
|
||||
|
||||
i3-wm (4.5.1-1) experimental; urgency=low
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Build-Depends: debhelper (>= 7.0.50~),
|
|||
libxcb-xinerama0-dev (>= 1.1),
|
||||
libxcb-randr0-dev,
|
||||
libxcb-icccm4-dev,
|
||||
libxcursor-dev,
|
||||
libxcb-cursor-dev,
|
||||
asciidoc (>= 8.4.4),
|
||||
xmlto,
|
||||
docbook-xml,
|
||||
|
@ -21,7 +21,7 @@ Build-Depends: debhelper (>= 7.0.50~),
|
|||
libcairo2-dev,
|
||||
libpango1.0-dev,
|
||||
libpod-simple-perl
|
||||
Standards-Version: 3.9.3
|
||||
Standards-Version: 3.9.4
|
||||
Homepage: http://i3wm.org/
|
||||
|
||||
Package: i3
|
||||
|
|
|
@ -38,7 +38,7 @@ override_dh_auto_build:
|
|||
$(MAKE) -C docs
|
||||
|
||||
override_dh_installchangelogs:
|
||||
dh_installchangelogs RELEASE-NOTES-4.5.1
|
||||
dh_installchangelogs RELEASE-NOTES-4.6
|
||||
|
||||
override_dh_install:
|
||||
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
|
||||
|
|
|
@ -97,21 +97,18 @@ Contains forward definitions for all public functions, as well as
|
|||
doxygen-compatible comments (so if you want to get a bit more of the big
|
||||
picture, either browse all header files or use doxygen if you prefer that).
|
||||
|
||||
src/cfgparse.l::
|
||||
Contains the lexer for i3’s configuration file, written for +flex(1)+.
|
||||
|
||||
src/cfgparse.y::
|
||||
Contains the parser for i3’s configuration file, written for +bison(1)+.
|
||||
src/config_parser.c::
|
||||
Contains a custom configuration parser. See src/command_parser.c for rationale
|
||||
on why we use a custom parser.
|
||||
|
||||
src/click.c::
|
||||
Contains all functions which handle mouse button clicks (right mouse button
|
||||
clicks initiate resizing and thus are relatively complex).
|
||||
|
||||
src/cmdparse.l::
|
||||
Contains the lexer for i3 commands, written for +flex(1)+.
|
||||
|
||||
src/cmdparse.y::
|
||||
Contains the parser for i3 commands, written for +bison(1)+.
|
||||
src/command_parser.c::
|
||||
Contains a hand-written parser to parse commands (commands are what
|
||||
you bind on keys and what you can send to i3 using the IPC interface, like
|
||||
'move left' or 'workspace 4').
|
||||
|
||||
src/con.c::
|
||||
Contains all functions which deal with containers directly (creating
|
||||
|
|
3
docs/ipc
3
docs/ipc
|
@ -494,6 +494,8 @@ font (string)::
|
|||
The font to use for text on the bar.
|
||||
workspace_buttons (boolean)::
|
||||
Display workspace buttons or not? Defaults to true.
|
||||
binding_mode_indicator (boolean)::
|
||||
Display the mode indicator or not? Defaults to true.
|
||||
verbose (boolean)::
|
||||
Should the bar enable verbose output for debugging? Defaults to false.
|
||||
colors (map)::
|
||||
|
@ -539,6 +541,7 @@ urgent_workspace_text/urgent_workspace_bar::
|
|||
"status_command": "i3status",
|
||||
"font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1",
|
||||
"workspace_buttons": true,
|
||||
"binding_mode_indicator": true,
|
||||
"verbose": false,
|
||||
"colors": {
|
||||
"background": "#c0c0c0",
|
||||
|
|
|
@ -252,7 +252,7 @@ workspace node. By default, the workspace node’s orientation is +horizontal+.
|
|||
Now you move one of these terminals down (+$mod+k+ by default). The workspace
|
||||
node’s orientation will be changed to +vertical+. The terminal window you moved
|
||||
down is directly attached to the workspace and appears on the bottom of the
|
||||
screen. A new (horizontal) container was created to accomodate the other two
|
||||
screen. A new (horizontal) container was created to accommodate the other two
|
||||
terminal windows. You will notice this when switching to tabbed mode (for
|
||||
example). You would end up having one tab called "another container" and the
|
||||
other one being the terminal window you moved down.
|
||||
|
@ -446,7 +446,7 @@ New workspaces get a reasonable default orientation: Wide-screen monitors
|
|||
(anything higher than wide) get vertical orientation.
|
||||
|
||||
With the +default_orientation+ configuration directive, you can override that
|
||||
behaviour.
|
||||
behavior.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------------
|
||||
|
@ -570,11 +570,12 @@ set $m Mod1
|
|||
bindsym $m+Shift+r restart
|
||||
------------------------
|
||||
|
||||
Variables are directly replaced in the file when parsing. There is no fancy
|
||||
handling and there are absolutely no plans to change this. If you need a more
|
||||
dynamic configuration you should create a little script which generates a
|
||||
configuration file and run it before starting i3 (for example in your
|
||||
+~/.xsession+ file).
|
||||
Variables are directly replaced in the file when parsing. Variables expansion
|
||||
is not recursive so it is not possible to define a variable with a value
|
||||
containing another variable. There is no fancy handling and there are
|
||||
absolutely no plans to change this. If you need a more dynamic configuration
|
||||
you should create a little script which generates a configuration file and run
|
||||
it before starting i3 (for example in your +~/.xsession+ file).
|
||||
|
||||
=== Automatically putting clients on specific workspaces
|
||||
|
||||
|
@ -833,7 +834,7 @@ popup_during_fullscreen smart
|
|||
When being in a tabbed or stacked container, the first container will be
|
||||
focused when you use +focus down+ on the last container -- the focus wraps. If
|
||||
however there is another stacked/tabbed container in that direction, focus will
|
||||
be set on that container. This is the default behaviour so you can navigate to
|
||||
be set on that container. This is the default behavior so you can navigate to
|
||||
all your windows without having to use +focus parent+.
|
||||
|
||||
If you want the focus to *always* wrap and you are aware of using +focus
|
||||
|
@ -899,7 +900,7 @@ workspace_auto_back_and_forth yes
|
|||
|
||||
If an application on another workspace sets an urgency hint, switching to this
|
||||
workspace may lead to immediate focus of the application, which also means the
|
||||
window decoration color would be immediately resetted to +client.focused+. This
|
||||
window decoration color would be immediately reseted to +client.focused+. This
|
||||
may make it unnecessarily hard to tell which window originally raised the
|
||||
event.
|
||||
|
||||
|
@ -1179,11 +1180,32 @@ workspace_buttons <yes|no>
|
|||
--------------------------
|
||||
|
||||
*Example*:
|
||||
--------------------
|
||||
------------------------
|
||||
bar {
|
||||
workspace_buttons no
|
||||
}
|
||||
--------------------
|
||||
------------------------
|
||||
|
||||
=== Binding Mode indicator
|
||||
|
||||
Specifies whether the current binding mode indicator should be shown or not.
|
||||
This is useful if you want to hide the workspace buttons but still be able
|
||||
to see the current binding mode indicator.
|
||||
For an example of a +mode+ definition, see <<resizingconfig>>.
|
||||
|
||||
The default is to show the mode indicator.
|
||||
|
||||
*Syntax*:
|
||||
-------------------------------
|
||||
binding_mode_indicator <yes|no>
|
||||
-------------------------------
|
||||
|
||||
*Example*:
|
||||
-----------------------------
|
||||
bar {
|
||||
binding_mode_indicator no
|
||||
}
|
||||
-----------------------------
|
||||
|
||||
=== Colors
|
||||
|
||||
|
@ -1210,7 +1232,7 @@ inactive_workspace::
|
|||
will be the case for most workspaces.
|
||||
urgent_workspace::
|
||||
Border, background and text color for a workspace button when the workspace
|
||||
window with the urgency hint set.
|
||||
contains a window with the urgency hint set. Also applies to +mode+ indicators.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------
|
||||
|
@ -1263,16 +1285,28 @@ bindsym $mod+x move container to workspace 3; workspace 3
|
|||
[[command_criteria]]
|
||||
|
||||
Furthermore, you can change the scope of a command - that is, which containers
|
||||
should be affected by that command, by using various criteria. These are
|
||||
prefixed in square brackets to every command. If you want to kill all windows
|
||||
which have the class Firefox, use:
|
||||
should be affected by that command, by using various criteria. The criteria
|
||||
are specified before any command in a pair of square brackets and are separated
|
||||
by space.
|
||||
|
||||
When using multiple commands, separate them by using a +,+ (a comma) instead of
|
||||
a semicolon. Criteria apply only until the next semicolon, so if you use a
|
||||
semicolon to separate commands, only the first one will be executed for the
|
||||
matched window(s).
|
||||
|
||||
*Example*:
|
||||
------------------------------------
|
||||
# if you want to kill all windows which have the class Firefox, use:
|
||||
bindsym $mod+x [class="Firefox"] kill
|
||||
|
||||
# same thing, but case-insensitive
|
||||
bindsym $mod+x [class="(?i)firefox"] kill
|
||||
|
||||
# kill only the About dialog from Firefox
|
||||
bindsym $mod+x [class="Firefox" window_role="About"] kill
|
||||
|
||||
# enable floating mode and move container to workspace 4
|
||||
for_window [class="^evil-app$"] floating enable, move container to workspace 4
|
||||
------------------------------------
|
||||
|
||||
The criteria which are currently implemented are:
|
||||
|
@ -1553,7 +1587,7 @@ specify a default name if there's currently no workspace starting with a "1".
|
|||
You can rename workspaces. This might be useful to start with the default
|
||||
numbered workspaces, do your work, and rename the workspaces afterwards to
|
||||
reflect what’s actually on them. You can also omit the old name to rename
|
||||
the currently focused workspace. This is handy if you wan't to use the
|
||||
the currently focused workspace. This is handy if you want to use the
|
||||
rename command with +i3-input+.
|
||||
|
||||
*Syntax*:
|
||||
|
@ -1568,7 +1602,7 @@ i3-msg 'rename workspace 5 to 6'
|
|||
i3-msg 'rename workspace 1 to "1: www"'
|
||||
i3-msg 'rename workspace "1: www" to "10: www"'
|
||||
i3-msg 'rename workspace to "2: mail"
|
||||
bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: '
|
||||
bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: '
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
=== Moving workspaces to a different screen
|
||||
|
|
|
@ -791,17 +791,17 @@ int main(int argc, char *argv[]) {
|
|||
close(fd);
|
||||
unlink(config_path);
|
||||
|
||||
int screen;
|
||||
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
errx(1, "Cannot open display\n");
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = "/tmp/i3-ipc.sock";
|
||||
|
||||
int screens;
|
||||
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
errx(1, "Cannot open display\n");
|
||||
|
||||
keysyms = xcb_key_symbols_alloc(conn);
|
||||
xcb_get_modifier_mapping_cookie_t modmap_cookie;
|
||||
modmap_cookie = xcb_get_modifier_mapping(conn);
|
||||
|
@ -813,7 +813,7 @@ int main(int argc, char *argv[]) {
|
|||
#include "atoms.xmacro"
|
||||
#undef xmacro
|
||||
|
||||
root_screen = xcb_aux_get_screen(conn, screens);
|
||||
root_screen = xcb_aux_get_screen(conn, screen);
|
||||
root = root_screen->root;
|
||||
|
||||
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
|
||||
|
|
|
@ -90,7 +90,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
char *shmname = root_atom_contents("I3_SHMLOG_PATH");
|
||||
char *shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0);
|
||||
if (shmname == NULL) {
|
||||
/* Something failed. Let’s invest a little effort to find out what it
|
||||
* is. This is hugely helpful for users who want to debug i3 but are
|
||||
|
@ -109,7 +109,7 @@ int main(int argc, char *argv[]) {
|
|||
fprintf(stderr, "FYI: The DISPLAY environment variable is set to \"%s\".\n", getenv("DISPLAY"));
|
||||
exit(1);
|
||||
}
|
||||
if (root_atom_contents("I3_CONFIG_PATH") != NULL) {
|
||||
if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) {
|
||||
fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n");
|
||||
if (!is_debug_build()) {
|
||||
fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION);
|
||||
|
|
|
@ -368,23 +368,23 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
printf("using format \"%s\"\n", format);
|
||||
|
||||
int screen;
|
||||
conn = xcb_connect(NULL, &screen);
|
||||
if (!conn || xcb_connection_has_error(conn))
|
||||
die("Cannot open display\n");
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen);
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = "/tmp/i3-ipc.sock";
|
||||
|
||||
sockfd = ipc_connect(socket_path);
|
||||
|
||||
int screens;
|
||||
conn = xcb_connect(NULL, &screens);
|
||||
if (!conn || xcb_connection_has_error(conn))
|
||||
die("Cannot open display\n");
|
||||
|
||||
/* Request the current InputFocus to restore when i3-input exits. */
|
||||
focus_cookie = xcb_get_input_focus(conn);
|
||||
|
||||
root_screen = xcb_aux_get_screen(conn, screens);
|
||||
root_screen = xcb_aux_get_screen(conn, screen);
|
||||
root = root_screen->root;
|
||||
|
||||
symbols = xcb_key_symbols_alloc(conn);
|
||||
|
|
|
@ -188,7 +188,7 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
if (socket_path == NULL)
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
|
||||
|
||||
/* Fall back to the default socket path */
|
||||
if (socket_path == NULL)
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <fcntl.h>
|
||||
#include <paths.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
@ -95,15 +96,8 @@ static void start_application(const char *command) {
|
|||
/* Child process */
|
||||
setsid();
|
||||
if (fork() == 0) {
|
||||
/* Stores the path of the shell */
|
||||
static const char *shell = NULL;
|
||||
|
||||
if (shell == NULL)
|
||||
if ((shell = getenv("SHELL")) == NULL)
|
||||
shell = "/bin/sh";
|
||||
|
||||
/* This is the child */
|
||||
execl(shell, shell, "-c", command, (void*)NULL);
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL);
|
||||
/* not reached */
|
||||
}
|
||||
exit(0);
|
||||
|
@ -165,8 +159,9 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
|||
fclose(script);
|
||||
|
||||
char *link_path;
|
||||
char *exe_path = get_exe_path(argv0);
|
||||
sasprintf(&link_path, "%s.nagbar_cmd", script_path);
|
||||
symlink(get_exe_path(argv0), link_path);
|
||||
symlink(exe_path, link_path);
|
||||
|
||||
char *terminal_cmd;
|
||||
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path);
|
||||
|
@ -178,6 +173,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
|||
free(link_path);
|
||||
free(terminal_cmd);
|
||||
free(script_path);
|
||||
free(exe_path);
|
||||
|
||||
/* TODO: unset flag, re-render */
|
||||
}
|
||||
|
@ -198,8 +194,12 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
|||
4 + 4, 4 + 4, rect.width - 4 - 4);
|
||||
|
||||
/* render close button */
|
||||
const char *close_button_label = "X";
|
||||
int line_width = 4;
|
||||
int w = 20;
|
||||
/* set width to the width of the label */
|
||||
int w = predict_text_width(i3string_from_utf8(close_button_label));
|
||||
/* account for left/right padding, which seems to be set to 8px (total) below */
|
||||
w += 8;
|
||||
int y = rect.width;
|
||||
uint32_t values[3];
|
||||
values[0] = color_button_background;
|
||||
|
@ -221,7 +221,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
|||
|
||||
values[0] = 1;
|
||||
set_font_colors(pixmap_gc, color_text, color_button_background);
|
||||
draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
|
||||
/* the x term here seems to set left/right padding */
|
||||
draw_text_ascii(close_button_label, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
|
||||
4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
|
||||
y -= w;
|
||||
|
||||
|
@ -230,8 +231,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
|||
/* render custom buttons */
|
||||
line_width = 1;
|
||||
for (int c = 0; c < buttoncnt; c++) {
|
||||
/* TODO: make w = text extents of the label */
|
||||
w = 100;
|
||||
/* set w to the width of the label */
|
||||
w = predict_text_width(buttons[c].label);
|
||||
/* account for left/right padding, which seems to be set to 12px (total) below */
|
||||
w += 12;
|
||||
y -= 30;
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background });
|
||||
close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 };
|
||||
|
@ -252,6 +255,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
|||
values[0] = color_text;
|
||||
values[1] = color_button_background;
|
||||
set_font_colors(pixmap_gc, color_text, color_button_background);
|
||||
/* the x term seems to set left/right padding */
|
||||
draw_text(buttons[c].label, pixmap, pixmap_gc,
|
||||
y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
|
||||
|
||||
|
|
|
@ -84,6 +84,13 @@ bindsym Mod1+a focus parent
|
|||
# focus the child container
|
||||
#bindsym Mod1+d focus child
|
||||
|
||||
# move the currently focused window to the scratchpad
|
||||
bindsym Mod1+Shift+minus move scratchpad
|
||||
|
||||
# Show the next scratchpad window or hide the focused scratchpad window.
|
||||
# If there are multiple scratchpad windows, this command cycles through them.
|
||||
bindsym Mod1+minus scratchpad show
|
||||
|
||||
# switch to workspace
|
||||
bindsym Mod1+1 workspace 1
|
||||
bindsym Mod1+2 workspace 2
|
||||
|
|
|
@ -23,7 +23,8 @@ typedef struct config_t {
|
|||
position_t position;
|
||||
int verbose;
|
||||
struct xcb_color_strings_t colors;
|
||||
int disable_ws;
|
||||
bool disable_binding_mode_indicator;
|
||||
bool disable_ws;
|
||||
char *bar_id;
|
||||
char *command;
|
||||
char *fontname;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
@ -22,6 +23,7 @@
|
|||
#include <yajl/yajl_parse.h>
|
||||
#include <yajl/yajl_version.h>
|
||||
#include <yajl/yajl_gen.h>
|
||||
#include <paths.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
@ -59,6 +61,58 @@ char *statusline_buffer = NULL;
|
|||
|
||||
int child_stdin;
|
||||
|
||||
/*
|
||||
* Clears all blocks from the statusline structure in memory and frees their
|
||||
* associated resources.
|
||||
*/
|
||||
static void clear_status_blocks() {
|
||||
struct status_block *first;
|
||||
while (!TAILQ_EMPTY(&statusline_head)) {
|
||||
first = TAILQ_FIRST(&statusline_head);
|
||||
I3STRING_FREE(first->full_text);
|
||||
TAILQ_REMOVE(&statusline_head, first, blocks);
|
||||
free(first);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Replaces the statusline in memory with an error message. Pass a format
|
||||
* string and format parameters as you would in `printf'. The next time
|
||||
* `draw_bars' is called, the error message text will be drawn on the bar in
|
||||
* the space allocated for the statusline.
|
||||
*/
|
||||
|
||||
/* forward function declaration is needed to add __attribute__ mechanism which
|
||||
* helps the compiler understand we are defining a printf wrapper */
|
||||
static void set_statusline_error(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
|
||||
static void set_statusline_error(const char *format, ...) {
|
||||
clear_status_blocks();
|
||||
|
||||
char *message;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vasprintf(&message, format, args);
|
||||
|
||||
struct status_block *err_block = scalloc(sizeof(struct status_block));
|
||||
err_block->full_text = i3string_from_utf8("Error: ");
|
||||
err_block->name = "error";
|
||||
err_block->color = "red";
|
||||
err_block->no_separator = true;
|
||||
|
||||
struct status_block *message_block = scalloc(sizeof(struct status_block));
|
||||
message_block->full_text = i3string_from_utf8(message);
|
||||
message_block->name = "error_message";
|
||||
message_block->color = "red";
|
||||
message_block->no_separator = true;
|
||||
|
||||
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
|
||||
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
|
||||
|
||||
FREE(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop and free() the stdin- and sigchild-watchers
|
||||
*
|
||||
|
@ -240,6 +294,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
|
|||
/* end of file, kill the watcher */
|
||||
ELOG("stdin: received EOF\n");
|
||||
cleanup();
|
||||
set_statusline_error("Received EOF from statusline process");
|
||||
draw_bars(false);
|
||||
*ret_buffer_len = -1;
|
||||
return NULL;
|
||||
|
@ -279,8 +334,18 @@ static bool read_json_input(unsigned char *input, int length) {
|
|||
#else
|
||||
if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
|
||||
#endif
|
||||
fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
|
||||
status, length, input);
|
||||
char *message = (char *)yajl_get_error(parser, 0, input, length);
|
||||
|
||||
/* strip the newline yajl adds to the error message */
|
||||
if (message[strlen(message) - 1] == '\n')
|
||||
message[strlen(message) - 1] = '\0';
|
||||
|
||||
fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
|
||||
status, message, length, input);
|
||||
|
||||
set_statusline_error("Could not parse JSON (%s)", message);
|
||||
yajl_free_error(parser, (unsigned char*)message);
|
||||
draw_bars(false);
|
||||
} else if (parser_context.has_urgent) {
|
||||
has_urgent = true;
|
||||
}
|
||||
|
@ -350,10 +415,23 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
|||
*
|
||||
*/
|
||||
void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
|
||||
int exit_status = WEXITSTATUS(watcher->rstatus);
|
||||
|
||||
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
|
||||
child.pid,
|
||||
watcher->rstatus);
|
||||
exit_status);
|
||||
|
||||
/* this error is most likely caused by a user giving a nonexecutable or
|
||||
* nonexistent file, so we will handle those cases separately. */
|
||||
if (exit_status == 126)
|
||||
set_statusline_error("status_command is not executable (exit %d)", exit_status);
|
||||
else if (exit_status == 127)
|
||||
set_statusline_error("status_command not found (exit %d)", exit_status);
|
||||
else
|
||||
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
|
||||
|
||||
cleanup();
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
void child_write_output(void) {
|
||||
|
@ -423,12 +501,8 @@ void start_child(char *command) {
|
|||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
dup2(pipe_out[0], STDIN_FILENO);
|
||||
|
||||
static const char *shell = NULL;
|
||||
|
||||
if ((shell = getenv("SHELL")) == NULL)
|
||||
shell = "/bin/sh";
|
||||
|
||||
execl(shell, shell, "-c", command, (char*) NULL);
|
||||
setpgid(child.pid, 0);
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL);
|
||||
return;
|
||||
default:
|
||||
/* Parent-process. Reroute streams */
|
||||
|
@ -511,8 +585,8 @@ void send_block_clicked(int button, const char *name, const char *instance, int
|
|||
void kill_child_at_exit(void) {
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
kill(child.pid, child.cont_signal);
|
||||
kill(child.pid, SIGTERM);
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,8 +598,8 @@ void kill_child_at_exit(void) {
|
|||
void kill_child(void) {
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
kill(child.pid, child.cont_signal);
|
||||
kill(child.pid, SIGTERM);
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
int status;
|
||||
waitpid(child.pid, &status, 0);
|
||||
cleanup();
|
||||
|
@ -539,7 +613,7 @@ void kill_child(void) {
|
|||
void stop_child(void) {
|
||||
if (child.stop_signal > 0 && !child.stopped) {
|
||||
child.stopped = true;
|
||||
kill(child.pid, child.stop_signal);
|
||||
killpg(child.pid, child.stop_signal);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,6 +624,6 @@ void stop_child(void) {
|
|||
void cont_child(void) {
|
||||
if (child.cont_signal > 0 && child.stopped) {
|
||||
child.stopped = false;
|
||||
kill(child.pid, child.cont_signal);
|
||||
killpg(child.pid, child.cont_signal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,6 +193,12 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
|
|||
*
|
||||
*/
|
||||
static int config_boolean_cb(void *params_, int val) {
|
||||
if (!strcmp(cur_key, "binding_mode_indicator")) {
|
||||
DLOG("binding_mode_indicator = %d\n", val);
|
||||
config.disable_binding_mode_indicator = !val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "workspace_buttons")) {
|
||||
DLOG("workspace_buttons = %d\n", val);
|
||||
config.disable_ws = !val;
|
||||
|
|
191
i3bar/src/xcb.c
191
i3bar/src/xcb.c
|
@ -59,6 +59,9 @@ xcb_connection_t *conn;
|
|||
/* The font we'll use */
|
||||
static i3Font font;
|
||||
|
||||
/* Overall height of the bar (based on font size) */
|
||||
int bar_height;
|
||||
|
||||
/* These are only relevant for XKB, which we only need for grabbing modifiers */
|
||||
Display *xkb_dpy;
|
||||
int xkb_event_base;
|
||||
|
@ -240,9 +243,9 @@ void unhide_bars(void) {
|
|||
values[0] = walk->rect.x;
|
||||
if (config.position == POS_TOP)
|
||||
values[1] = walk->rect.y;
|
||||
else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
|
||||
else values[1] = walk->rect.y + walk->rect.h - bar_height;
|
||||
values[2] = walk->rect.w;
|
||||
values[3] = font.height + 6;
|
||||
values[3] = bar_height;
|
||||
values[4] = XCB_STACK_MODE_ABOVE;
|
||||
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
|
||||
cookie = xcb_configure_window_checked(xcb_connection,
|
||||
|
@ -430,8 +433,9 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Configures the x coordinate of all trayclients. To be called after adding a
|
||||
* new tray client or removing an old one.
|
||||
* Adjusts the size of the tray window and alignment of the tray clients by
|
||||
* configuring their respective x coordinates. To be called when mapping or
|
||||
* unmapping a tray client window.
|
||||
*
|
||||
*/
|
||||
static void configure_trayclients(void) {
|
||||
|
@ -607,7 +611,6 @@ static void handle_client_message(xcb_client_message_event_t* event) {
|
|||
}
|
||||
trayclient *tc = smalloc(sizeof(trayclient));
|
||||
tc->win = client;
|
||||
tc->mapped = map_it;
|
||||
tc->xe_version = xe_version;
|
||||
TAILQ_INSERT_TAIL(output->trayclients, tc, tailq);
|
||||
|
||||
|
@ -620,8 +623,69 @@ static void handle_client_message(xcb_client_message_event_t* event) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Handles UnmapNotify events. These events happen when a tray window unmaps
|
||||
* itself. We then update our data structure
|
||||
* Handles DestroyNotify events by removing the tray client from the data
|
||||
* structure. According to the XEmbed protocol, this is one way for a tray
|
||||
* client to finish the protocol. After this event is received, there is no
|
||||
* further interaction with the tray client.
|
||||
*
|
||||
* See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
|
||||
*
|
||||
*/
|
||||
static void handle_destroy_notify(xcb_destroy_notify_event_t* event) {
|
||||
DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH(walk, outputs, slist) {
|
||||
if (!walk->active)
|
||||
continue;
|
||||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window)
|
||||
continue;
|
||||
|
||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles MapNotify events. These events happen when a tray client shows its
|
||||
* window. We respond by realigning the tray clients.
|
||||
*
|
||||
*/
|
||||
static void handle_map_notify(xcb_map_notify_event_t* event) {
|
||||
DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event);
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH(walk, outputs, slist) {
|
||||
if (!walk->active)
|
||||
continue;
|
||||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window)
|
||||
continue;
|
||||
|
||||
DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||
trayclient->mapped = true;
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Handles UnmapNotify events. These events happen when a tray client hides its
|
||||
* window. We respond by realigning the tray clients.
|
||||
*
|
||||
*/
|
||||
static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
|
||||
|
@ -637,8 +701,8 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
|
|||
if (trayclient->win != event->window)
|
||||
continue;
|
||||
|
||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
||||
DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window);
|
||||
trayclient->mapped = false;
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
|
@ -705,15 +769,9 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
|
|||
if (trayclient->mapped && !map_it) {
|
||||
/* need to unmap the window */
|
||||
xcb_unmap_window(xcb_connection, trayclient->win);
|
||||
trayclient->mapped = map_it;
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
} else if (!trayclient->mapped && map_it) {
|
||||
/* need to map the window */
|
||||
xcb_map_window(xcb_connection, trayclient->win);
|
||||
trayclient->mapped = map_it;
|
||||
configure_trayclients();
|
||||
draw_bars(false);
|
||||
}
|
||||
free(xembedr);
|
||||
}
|
||||
|
@ -795,11 +853,17 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
|||
* example system tray widgets talk to us directly via client messages. */
|
||||
handle_client_message((xcb_client_message_event_t*) event);
|
||||
break;
|
||||
case XCB_UNMAP_NOTIFY:
|
||||
case XCB_DESTROY_NOTIFY:
|
||||
/* UnmapNotifies are received when a tray window unmaps itself */
|
||||
/* DestroyNotify signifies the end of the XEmbed protocol */
|
||||
handle_destroy_notify((xcb_destroy_notify_event_t*) event);
|
||||
break;
|
||||
case XCB_UNMAP_NOTIFY:
|
||||
/* UnmapNotify is received when a tray client hides its window. */
|
||||
handle_unmap_notify((xcb_unmap_notify_event_t*) event);
|
||||
break;
|
||||
case XCB_MAP_NOTIFY:
|
||||
handle_map_notify((xcb_map_notify_event_t*) event);
|
||||
break;
|
||||
case XCB_PROPERTY_NOTIFY:
|
||||
/* PropertyNotify */
|
||||
handle_property_notify((xcb_property_notify_event_t*) event);
|
||||
|
@ -957,26 +1021,7 @@ char *init_xcb_early() {
|
|||
/* Now we get the atoms and save them in a nice data structure */
|
||||
get_atoms();
|
||||
|
||||
xcb_get_property_cookie_t path_cookie;
|
||||
path_cookie = xcb_get_property_unchecked(xcb_connection,
|
||||
0,
|
||||
xcb_root,
|
||||
atoms[I3_SOCKET_PATH],
|
||||
XCB_GET_PROPERTY_TYPE_ANY,
|
||||
0, PATH_MAX);
|
||||
|
||||
/* We check, if i3 set its socket-path */
|
||||
xcb_get_property_reply_t *path_reply = xcb_get_property_reply(xcb_connection,
|
||||
path_cookie,
|
||||
NULL);
|
||||
char *path = NULL;
|
||||
if (path_reply) {
|
||||
int len = xcb_get_property_value_length(path_reply);
|
||||
if (len != 0) {
|
||||
path = strndup(xcb_get_property_value(path_reply), len);
|
||||
}
|
||||
}
|
||||
|
||||
char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen);
|
||||
|
||||
if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") ||
|
||||
xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") ||
|
||||
|
@ -1061,6 +1106,7 @@ void init_xcb_late(char *fontname) {
|
|||
font = load_font(fontname, true);
|
||||
set_font(&font);
|
||||
DLOG("Calculated Font-height: %d\n", font.height);
|
||||
bar_height = font.height + 6;
|
||||
|
||||
xcb_flush(xcb_connection);
|
||||
|
||||
|
@ -1334,7 +1380,7 @@ void realloc_sl_buffer(void) {
|
|||
statusline_pm,
|
||||
xcb_root,
|
||||
MAX(root_screen->width_in_pixels, statusline_width),
|
||||
root_screen->height_in_pixels);
|
||||
bar_height);
|
||||
|
||||
uint32_t mask = XCB_GC_FOREGROUND;
|
||||
uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
|
||||
|
@ -1407,8 +1453,8 @@ void reconfig_windows(bool redraw_bars) {
|
|||
root_screen->root_depth,
|
||||
walk->bar,
|
||||
xcb_root,
|
||||
walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
|
||||
walk->rect.w, font.height + 6,
|
||||
walk->rect.x, walk->rect.y + walk->rect.h - bar_height,
|
||||
walk->rect.w, bar_height,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
root_screen->root_visual,
|
||||
|
@ -1421,7 +1467,7 @@ void reconfig_windows(bool redraw_bars) {
|
|||
walk->buffer,
|
||||
walk->bar,
|
||||
walk->rect.w,
|
||||
walk->rect.h);
|
||||
bar_height);
|
||||
|
||||
/* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */
|
||||
xcb_void_cookie_t class_cookie;
|
||||
|
@ -1482,12 +1528,12 @@ void reconfig_windows(bool redraw_bars) {
|
|||
case POS_NONE:
|
||||
break;
|
||||
case POS_TOP:
|
||||
strut_partial.top = font.height + 6;
|
||||
strut_partial.top = bar_height;
|
||||
strut_partial.top_start_x = walk->rect.x;
|
||||
strut_partial.top_end_x = walk->rect.x + walk->rect.w;
|
||||
break;
|
||||
case POS_BOT:
|
||||
strut_partial.bottom = font.height + 6;
|
||||
strut_partial.bottom = bar_height;
|
||||
strut_partial.bottom_start_x = walk->rect.x;
|
||||
strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
|
||||
break;
|
||||
|
@ -1527,10 +1573,20 @@ void reconfig_windows(bool redraw_bars) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!tray_configured &&
|
||||
(!config.tray_output ||
|
||||
strcasecmp("none", config.tray_output) != 0)) {
|
||||
const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name);
|
||||
if (!tray_configured && strcasecmp(tray_output, "none") != 0) {
|
||||
/* Configuration sanity check: ensure this i3bar instance handles the output on
|
||||
* which the tray should appear (e.g. don’t initialize a tray if tray_output ==
|
||||
* VGA-1 but output == [HDMI-1]).
|
||||
*/
|
||||
i3_output *output;
|
||||
SLIST_FOREACH(output, outputs, slist) {
|
||||
if (strcasecmp(output->name, tray_output) == 0 ||
|
||||
(strcasecmp(tray_output, "primary") == 0 && output->primary)) {
|
||||
init_tray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
tray_configured = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -1541,9 +1597,9 @@ void reconfig_windows(bool redraw_bars) {
|
|||
XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
values[0] = walk->rect.x;
|
||||
values[1] = walk->rect.y + walk->rect.h - font.height - 6;
|
||||
values[1] = walk->rect.y + walk->rect.h - bar_height;
|
||||
values[2] = walk->rect.w;
|
||||
values[3] = font.height + 6;
|
||||
values[3] = bar_height;
|
||||
values[4] = XCB_STACK_MODE_ABOVE;
|
||||
|
||||
DLOG("Destroying buffer for output %s\n", walk->name);
|
||||
|
@ -1569,7 +1625,7 @@ void reconfig_windows(bool redraw_bars) {
|
|||
walk->buffer,
|
||||
walk->bar,
|
||||
walk->rect.w,
|
||||
walk->rect.h);
|
||||
bar_height);
|
||||
|
||||
xcb_void_cookie_t map_cookie, umap_cookie;
|
||||
if (redraw_bars) {
|
||||
|
@ -1612,9 +1668,6 @@ void draw_bars(bool unhide) {
|
|||
|
||||
refresh_statusline();
|
||||
|
||||
static char *last_urgent_ws = NULL;
|
||||
bool walks_away = true;
|
||||
|
||||
i3_output *outputs_walk;
|
||||
SLIST_FOREACH(outputs_walk, outputs, slist) {
|
||||
if (!outputs_walk->active) {
|
||||
|
@ -1631,7 +1684,7 @@ void draw_bars(bool unhide) {
|
|||
outputs_walk->bargc,
|
||||
XCB_GC_FOREGROUND,
|
||||
&color);
|
||||
xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
|
||||
xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, bar_height };
|
||||
xcb_poly_fill_rectangle(xcb_connection,
|
||||
outputs_walk->buffer,
|
||||
outputs_walk->bargc,
|
||||
|
@ -1666,14 +1719,11 @@ void draw_bars(bool unhide) {
|
|||
MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
|
||||
}
|
||||
|
||||
if (config.disable_ws) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!config.disable_ws) {
|
||||
i3_ws *ws_walk;
|
||||
|
||||
TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) {
|
||||
DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
|
||||
DLOG("Drawing Button for WS %s at x = %d, len = %d\n",
|
||||
i3string_as_utf8(ws_walk->name), i, ws_walk->name_width);
|
||||
uint32_t fg_color = colors.inactive_ws_fg;
|
||||
uint32_t bg_color = colors.inactive_ws_bg;
|
||||
uint32_t border_color = colors.inactive_ws_border;
|
||||
|
@ -1686,8 +1736,6 @@ void draw_bars(bool unhide) {
|
|||
fg_color = colors.focus_ws_fg;
|
||||
bg_color = colors.focus_ws_bg;
|
||||
border_color = colors.focus_ws_border;
|
||||
if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), last_urgent_ws) == 0)
|
||||
walks_away = false;
|
||||
}
|
||||
}
|
||||
if (ws_walk->urgent) {
|
||||
|
@ -1696,10 +1744,6 @@ void draw_bars(bool unhide) {
|
|||
bg_color = colors.urgent_ws_bg;
|
||||
border_color = colors.urgent_ws_border;
|
||||
unhide = true;
|
||||
if (!ws_walk->focused) {
|
||||
FREE(last_urgent_ws);
|
||||
last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
|
||||
}
|
||||
}
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
|
||||
uint32_t vals_border[] = { border_color, border_color };
|
||||
|
@ -1725,13 +1769,14 @@ void draw_bars(bool unhide) {
|
|||
1,
|
||||
&rect);
|
||||
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
|
||||
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, ws_walk->name_width);
|
||||
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc,
|
||||
i + 5, 3, ws_walk->name_width);
|
||||
i += 10 + ws_walk->name_width + 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.name) {
|
||||
|
||||
if (binding.name && !config.disable_binding_mode_indicator) {
|
||||
uint32_t fg_color = colors.urgent_ws_fg;
|
||||
uint32_t bg_color = colors.urgent_ws_bg;
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
|
||||
|
@ -1770,13 +1815,11 @@ void draw_bars(bool unhide) {
|
|||
}
|
||||
|
||||
/* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */
|
||||
bool should_unhide = (config.hidden_state == S_SHOW || (unhide && config.hidden_state == S_HIDE));
|
||||
bool should_hide = (config.hide_on_modifier == M_INVISIBLE);
|
||||
|
||||
if (mod_pressed || (should_unhide && !should_hide)) {
|
||||
if (mod_pressed ||
|
||||
config.hidden_state == S_SHOW ||
|
||||
unhide) {
|
||||
unhide_bars();
|
||||
} else if (!mod_pressed && (walks_away || should_hide)) {
|
||||
FREE(last_urgent_ws);
|
||||
} else if (config.hide_on_modifier == M_HIDE) {
|
||||
hide_bars();
|
||||
}
|
||||
|
||||
|
|
|
@ -267,6 +267,10 @@ struct Barconfig {
|
|||
* zero. */
|
||||
bool hide_workspace_buttons;
|
||||
|
||||
/** Hide mode button? Configuration option is 'binding_mode_indicator no'
|
||||
* but we invert the bool for the same reason as hide_workspace_buttons.*/
|
||||
bool hide_binding_mode_indicator;
|
||||
|
||||
/** Enable verbose mode? Useful for debugging purposes. */
|
||||
bool verbose;
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ CFGFUN(bar_socket_path, const char *socket_path);
|
|||
CFGFUN(bar_tray_output, const char *output);
|
||||
CFGFUN(bar_color_single, const char *colorclass, const char *color);
|
||||
CFGFUN(bar_status_command, const char *command);
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||
CFGFUN(bar_workspace_buttons, const char *value);
|
||||
CFGFUN(bar_finish);
|
||||
|
||||
|
|
|
@ -134,14 +134,37 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
|
|||
|
||||
#endif
|
||||
/**
|
||||
* This function grabs your pointer and lets you drag stuff around (borders).
|
||||
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
|
||||
* and the given callback will be called with the parameters specified (client,
|
||||
* border on which the click originally was), the original rect of the client,
|
||||
* the event and the new coordinates (x, y).
|
||||
* This is the return value of a drag operation like drag_pointer.
|
||||
*
|
||||
* DRAGGING will indicate the drag action is still in progress and can be
|
||||
* continued or resolved.
|
||||
*
|
||||
* DRAG_SUCCESS will indicate the intention of the drag action should be
|
||||
* carried out.
|
||||
*
|
||||
* DRAG_REVERT will indicate an attempt should be made to restore the state of
|
||||
* the involved windows to their condition before the drag.
|
||||
*
|
||||
* DRAG_ABORT will indicate that the intention of the drag action cannot be
|
||||
* carried out (e.g. because the window has been unmapped).
|
||||
*
|
||||
*/
|
||||
void drag_pointer(Con *con, const xcb_button_press_event_t *event,
|
||||
typedef enum {
|
||||
DRAGGING = 0,
|
||||
DRAG_SUCCESS,
|
||||
DRAG_REVERT,
|
||||
DRAG_ABORT
|
||||
} drag_result_t;
|
||||
|
||||
/**
|
||||
* This function grabs your pointer and keyboard and lets you drag stuff around
|
||||
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
|
||||
* be received and the given callback will be called with the parameters
|
||||
* specified (client, border on which the click originally was), the original
|
||||
* rect of the client, the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, int cursor,
|
||||
callback_t callback, const void *extra);
|
||||
|
||||
|
|
|
@ -84,11 +84,14 @@ void errorlog(char *fmt, ...);
|
|||
* Try to get the contents of the given atom (for example I3_SOCKET_PATH) from
|
||||
* the X11 root window and return NULL if it doesn’t work.
|
||||
*
|
||||
* If the provided XCB connection is NULL, a new connection will be
|
||||
* established.
|
||||
*
|
||||
* The memory for the contents is dynamically allocated and has to be
|
||||
* free()d by the caller.
|
||||
*
|
||||
*/
|
||||
char *root_atom_contents(const char *atomname);
|
||||
char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen);
|
||||
|
||||
/**
|
||||
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
|
||||
|
@ -369,7 +372,8 @@ char *get_process_filename(const char *prefix);
|
|||
*
|
||||
* The implementation follows http://stackoverflow.com/a/933996/712014
|
||||
*
|
||||
* Returned value must be freed by the caller.
|
||||
*/
|
||||
const char *get_exe_path(const char *argv0);
|
||||
char *get_exe_path(const char *argv0);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#ifndef I3_RESIZE_H
|
||||
#define I3_RESIZE_H
|
||||
|
||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction);
|
||||
|
||||
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* xcursor.c: libXcursor support for themed cursors.
|
||||
*
|
||||
|
@ -10,7 +10,7 @@
|
|||
#ifndef I3_XCURSOR_CURSOR_H
|
||||
#define I3_XCURSOR_CURSOR_H
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
|
||||
enum xcursor_cursor_t {
|
||||
XCURSOR_CURSOR_POINTER = 0,
|
||||
|
@ -26,7 +26,7 @@ enum xcursor_cursor_t {
|
|||
};
|
||||
|
||||
void xcursor_load_cursors(void);
|
||||
Cursor xcursor_get_cursor(enum xcursor_cursor_t c);
|
||||
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c);
|
||||
int xcursor_get_xcb_cursor(enum xcursor_cursor_t c);
|
||||
|
||||
/**
|
||||
|
|
29
libi3/font.c
29
libi3/font.c
|
@ -30,6 +30,21 @@ static double pango_font_red;
|
|||
static double pango_font_green;
|
||||
static double pango_font_blue;
|
||||
|
||||
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
|
||||
PangoLayout *layout;
|
||||
PangoContext *context;
|
||||
|
||||
context = pango_cairo_create_context(cr);
|
||||
const double dpi = (double)root_screen->height_in_pixels * 25.4 /
|
||||
(double)root_screen->height_in_millimeters;
|
||||
LOG("X11 root window dictates %f DPI\n", dpi);
|
||||
pango_cairo_context_set_resolution(context, dpi);
|
||||
layout = pango_layout_new(context);
|
||||
g_object_unref(context);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads a Pango font description into an i3Font structure. Returns true
|
||||
* on success, false otherwise.
|
||||
|
@ -56,7 +71,7 @@ static bool load_pango_font(i3Font *font, const char *desc) {
|
|||
/* Create a dummy Pango layout to compute the font height */
|
||||
cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
PangoLayout *layout = pango_cairo_create_layout(cr);
|
||||
PangoLayout *layout = create_layout_with_dpi(cr);
|
||||
pango_layout_set_font_description(layout, font->specific.pango_desc);
|
||||
|
||||
/* Get the font height */
|
||||
|
@ -85,17 +100,21 @@ static void draw_text_pango(const char *text, size_t text_len,
|
|||
cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
|
||||
root_visual_type, x + max_width, y + savedFont->height);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
PangoLayout *layout = pango_cairo_create_layout(cr);
|
||||
PangoLayout *layout = create_layout_with_dpi(cr);
|
||||
gint height;
|
||||
|
||||
pango_layout_set_font_description(layout, savedFont->specific.pango_desc);
|
||||
pango_layout_set_width(layout, max_width * PANGO_SCALE);
|
||||
pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||
|
||||
pango_layout_set_text(layout, text, text_len);
|
||||
|
||||
/* Do the drawing */
|
||||
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
|
||||
cairo_move_to(cr, x, y);
|
||||
pango_layout_set_text(layout, text, text_len);
|
||||
pango_cairo_update_layout(cr, layout);
|
||||
pango_layout_get_pixel_size(layout, NULL, &height);
|
||||
cairo_move_to(cr, x, y - (height - savedFont->height));
|
||||
pango_cairo_show_layout(cr, layout);
|
||||
|
||||
/* Free resources */
|
||||
|
@ -113,7 +132,7 @@ static int predict_text_width_pango(const char *text, size_t text_len) {
|
|||
/* root_visual_type is cached in load_pango_font */
|
||||
cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
PangoLayout *layout = pango_cairo_create_layout(cr);
|
||||
PangoLayout *layout = create_layout_with_dpi(cr);
|
||||
|
||||
/* Get the font width */
|
||||
gint width;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "libi3.h"
|
||||
|
||||
|
@ -11,10 +12,14 @@
|
|||
*
|
||||
* The implementation follows http://stackoverflow.com/a/933996/712014
|
||||
*
|
||||
* Returned value must be freed by the caller.
|
||||
*/
|
||||
const char *get_exe_path(const char *argv0) {
|
||||
static char destpath[PATH_MAX];
|
||||
char tmp[PATH_MAX];
|
||||
char *get_exe_path(const char *argv0) {
|
||||
size_t destpath_size = 1024;
|
||||
size_t tmp_size = 1024;
|
||||
char *destpath = smalloc(destpath_size);
|
||||
char *tmp = smalloc(tmp_size);
|
||||
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
/* Linux and Debian/kFreeBSD provide /proc/self/exe */
|
||||
|
@ -25,30 +30,48 @@ const char *get_exe_path(const char *argv0) {
|
|||
#endif
|
||||
ssize_t linksize;
|
||||
|
||||
if ((linksize = readlink(exepath, destpath, sizeof(destpath) - 1)) != -1) {
|
||||
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
|
||||
destpath_size = destpath_size * 2;
|
||||
destpath = srealloc(destpath, destpath_size);
|
||||
}
|
||||
if (linksize != -1) {
|
||||
/* readlink() does not NULL-terminate strings, so we have to. */
|
||||
destpath[linksize] = '\0';
|
||||
|
||||
free(tmp);
|
||||
return destpath;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* argv[0] is most likely a full path if it starts with a slash. */
|
||||
if (argv0[0] == '/')
|
||||
return argv0;
|
||||
if (argv0[0] == '/') {
|
||||
free(tmp);
|
||||
free(destpath);
|
||||
return sstrdup(argv0);
|
||||
}
|
||||
|
||||
/* if argv[0] contains a /, prepend the working directory */
|
||||
if (strchr(argv0, '/') != NULL &&
|
||||
getcwd(tmp, sizeof(tmp)) != NULL) {
|
||||
snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0);
|
||||
if (strchr(argv0, '/') != NULL) {
|
||||
char *retgcwd;
|
||||
while ((retgcwd = getcwd(tmp, tmp_size)) == NULL && errno == ERANGE) {
|
||||
tmp_size = tmp_size * 2;
|
||||
tmp = srealloc(tmp, tmp_size);
|
||||
}
|
||||
if (retgcwd != NULL) {
|
||||
free(destpath);
|
||||
sasprintf(&destpath, "%s/%s", tmp, argv0);
|
||||
free(tmp);
|
||||
return destpath;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */
|
||||
char *path = getenv("PATH");
|
||||
if (path == NULL) {
|
||||
/* _CS_PATH is typically something like "/bin:/usr/bin" */
|
||||
confstr(_CS_PATH, tmp, sizeof(tmp));
|
||||
while (confstr(_CS_PATH, tmp, tmp_size) > tmp_size) {
|
||||
tmp_size = tmp_size * 2;
|
||||
tmp = srealloc(tmp, tmp_size);
|
||||
}
|
||||
sasprintf(&path, ":%s", tmp);
|
||||
} else {
|
||||
path = strdup(path);
|
||||
|
@ -59,16 +82,20 @@ const char *get_exe_path(const char *argv0) {
|
|||
if ((component = strtok(str, ":")) == NULL)
|
||||
break;
|
||||
str = NULL;
|
||||
snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0);
|
||||
free(destpath);
|
||||
sasprintf(&destpath, "%s/%s", component, argv0);
|
||||
/* Of course this is not 100% equivalent to actually exec()ing the
|
||||
* binary, but meh. */
|
||||
if (access(destpath, X_OK) == 0) {
|
||||
free(path);
|
||||
free(tmp);
|
||||
return destpath;
|
||||
}
|
||||
}
|
||||
free(destpath);
|
||||
free(path);
|
||||
free(tmp);
|
||||
|
||||
/* Last resort: maybe it’s in /usr/bin? */
|
||||
return "/usr/bin/i3-nagbar";
|
||||
return sstrdup("/usr/bin/i3-nagbar");
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "libi3.h"
|
||||
|
||||
|
@ -35,6 +36,9 @@ char *get_process_filename(const char *prefix) {
|
|||
struct stat buf;
|
||||
if (stat(dir, &buf) != 0) {
|
||||
if (mkdir(dir, 0700) == -1) {
|
||||
warn("Could not mkdir(%s)", dir);
|
||||
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
|
||||
getenv("XDG_RUNTIME_DIR"));
|
||||
perror("mkdir()");
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
@ -19,19 +21,23 @@
|
|||
* Try to get the contents of the given atom (for example I3_SOCKET_PATH) from
|
||||
* the X11 root window and return NULL if it doesn’t work.
|
||||
*
|
||||
* If the provided XCB connection is NULL, a new connection will be
|
||||
* established.
|
||||
*
|
||||
* The memory for the contents is dynamically allocated and has to be
|
||||
* free()d by the caller.
|
||||
*
|
||||
*/
|
||||
char *root_atom_contents(const char *atomname) {
|
||||
xcb_connection_t *conn;
|
||||
char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen) {
|
||||
xcb_intern_atom_cookie_t atom_cookie;
|
||||
xcb_intern_atom_reply_t *atom_reply;
|
||||
int screen;
|
||||
char *content;
|
||||
size_t content_max_words = 256;
|
||||
xcb_connection_t *conn = provided_conn;
|
||||
|
||||
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
if (provided_conn == NULL &&
|
||||
((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||
xcb_connection_has_error(conn)))
|
||||
return NULL;
|
||||
|
||||
atom_cookie = xcb_intern_atom(conn, 0, strlen(atomname), atomname);
|
||||
|
@ -46,21 +52,51 @@ char *root_atom_contents(const char *atomname) {
|
|||
xcb_get_property_cookie_t prop_cookie;
|
||||
xcb_get_property_reply_t *prop_reply;
|
||||
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
|
||||
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
|
||||
if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
|
||||
if (prop_reply == NULL) {
|
||||
free(atom_reply);
|
||||
return NULL;
|
||||
}
|
||||
if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) {
|
||||
/* We received an incomplete value. Ask again but with a properly
|
||||
* adjusted size. */
|
||||
content_max_words += ceil(prop_reply->bytes_after / 4.0);
|
||||
/* Repeat the request, with adjusted size */
|
||||
free(prop_reply);
|
||||
prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
|
||||
prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
|
||||
if (prop_reply == NULL) {
|
||||
free(atom_reply);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (xcb_get_property_value_length(prop_reply) == 0) {
|
||||
free(atom_reply);
|
||||
free(prop_reply);
|
||||
return NULL;
|
||||
}
|
||||
if (prop_reply->type == XCB_ATOM_CARDINAL) {
|
||||
/* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL
|
||||
* we query is I3_PID, which is 32-bit. */
|
||||
if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1)
|
||||
return NULL;
|
||||
} else {
|
||||
if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
|
||||
(char*)xcb_get_property_value(prop_reply)) == -1)
|
||||
if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1) {
|
||||
free(atom_reply);
|
||||
free(prop_reply);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply),
|
||||
(char*)xcb_get_property_value(prop_reply)) == -1) {
|
||||
free(atom_reply);
|
||||
free(prop_reply);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (provided_conn == NULL)
|
||||
xcb_disconnect(conn);
|
||||
free(atom_reply);
|
||||
free(prop_reply);
|
||||
return content;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ template::[header-declarations]
|
|||
<refentrytitle>{mantitle}</refentrytitle>
|
||||
<manvolnum>{manvolnum}</manvolnum>
|
||||
<refmiscinfo class="source">i3</refmiscinfo>
|
||||
<refmiscinfo class="version">4.6</refmiscinfo>
|
||||
<refmiscinfo class="version">4.7</refmiscinfo>
|
||||
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
|
||||
</refmeta>
|
||||
<refnamediv>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
i3-dump-log(1)
|
||||
==============
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
v4.1, December 2011
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
v4.6, September 2013
|
||||
|
||||
== NAME
|
||||
|
||||
|
@ -9,7 +9,7 @@ i3-dump-log - dumps the i3 SHM log
|
|||
|
||||
== SYNOPSIS
|
||||
|
||||
i3-dump-log [-s <socketpath>]
|
||||
i3-dump-log [-s <socketpath>] [-f]
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
|
@ -19,6 +19,9 @@ figuring out what is going on, without permanently logging to a file.
|
|||
|
||||
With i3-dump-log, you can dump the SHM log to stdout.
|
||||
|
||||
The -f flag works like tail -f, i.e. the process does not terminate after
|
||||
dumping the log, but prints new lines as they appear.
|
||||
|
||||
== EXAMPLE
|
||||
|
||||
i3-dump-log | gzip -9 > /tmp/i3-log.gz
|
||||
|
|
|
@ -209,11 +209,11 @@ state RESIZE_TILING:
|
|||
-> call cmd_resize($way, $direction, $resize_px, "10")
|
||||
|
||||
state RESIZE_TILING_OR:
|
||||
'ppt'
|
||||
->
|
||||
resize_ppt = word
|
||||
->
|
||||
end
|
||||
-> RESIZE_TILING_FINAL
|
||||
|
||||
state RESIZE_TILING_FINAL:
|
||||
'ppt', end
|
||||
-> call cmd_resize($way, $direction, $resize_px, $resize_ppt)
|
||||
|
||||
# rename workspace <name> to <name>
|
||||
|
|
|
@ -356,6 +356,7 @@ state BAR:
|
|||
'output' -> BAR_OUTPUT
|
||||
'tray_output' -> BAR_TRAY_OUTPUT
|
||||
'font' -> BAR_FONT
|
||||
'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR
|
||||
'workspace_buttons' -> BAR_WORKSPACE_BUTTONS
|
||||
'verbose' -> BAR_VERBOSE
|
||||
'colors' -> BAR_COLORS_BRACE
|
||||
|
@ -411,6 +412,10 @@ state BAR_FONT:
|
|||
font = string
|
||||
-> call cfg_bar_font($font); BAR
|
||||
|
||||
state BAR_BINDING_MODE_INDICATOR:
|
||||
value = word
|
||||
-> call cfg_bar_binding_mode_indicator($value); BAR
|
||||
|
||||
state BAR_WORKSPACE_BUTTONS:
|
||||
value = word
|
||||
-> call cfg_bar_workspace_buttons($value); BAR
|
||||
|
|
56
src/click.c
56
src/click.c
|
@ -28,45 +28,43 @@ typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_
|
|||
*/
|
||||
static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) {
|
||||
DLOG("border = %d, con = %p\n", border, con);
|
||||
char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n');
|
||||
orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ);
|
||||
|
||||
/* look for a parent container with the right orientation */
|
||||
Con *first = NULL, *second = NULL;
|
||||
Con *resize_con = con;
|
||||
while (resize_con->type != CT_WORKSPACE &&
|
||||
resize_con->type != CT_FLOATING_CON &&
|
||||
con_orientation(resize_con->parent) != orientation)
|
||||
resize_con = resize_con->parent;
|
||||
|
||||
DLOG("resize_con = %p\n", resize_con);
|
||||
if (resize_con->type != CT_WORKSPACE &&
|
||||
resize_con->type != CT_FLOATING_CON &&
|
||||
con_orientation(resize_con->parent) == orientation) {
|
||||
first = resize_con;
|
||||
second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes);
|
||||
if (second == TAILQ_END(&(first->nodes_head))) {
|
||||
second = NULL;
|
||||
}
|
||||
else if (way == 'p') {
|
||||
Con *tmp = first;
|
||||
first = second;
|
||||
second = tmp;
|
||||
}
|
||||
DLOG("first = %p, second = %p, resize_con = %p\n",
|
||||
first, second, resize_con);
|
||||
Con *second = NULL;
|
||||
Con *first = con;
|
||||
direction_t search_direction;
|
||||
switch (border) {
|
||||
case BORDER_LEFT:
|
||||
search_direction = D_LEFT;
|
||||
break;
|
||||
case BORDER_RIGHT:
|
||||
search_direction = D_RIGHT;
|
||||
break;
|
||||
case BORDER_TOP:
|
||||
search_direction = D_UP;
|
||||
break;
|
||||
case BORDER_BOTTOM:
|
||||
search_direction = D_DOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (first == NULL || second == NULL) {
|
||||
DLOG("Resize not possible\n");
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction);
|
||||
if (!res) {
|
||||
LOG("No second container in this direction found.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(first != second);
|
||||
assert(first->parent == second->parent);
|
||||
|
||||
/* The first container should always be in front of the second container */
|
||||
if (search_direction == D_UP || search_direction == D_LEFT) {
|
||||
Con *tmp = first;
|
||||
first = second;
|
||||
second = tmp;
|
||||
}
|
||||
|
||||
/* We modify the X/Y position in the event so that the divider line is at
|
||||
* the actual position of the border, not at the position of the click. */
|
||||
const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT);
|
||||
if (orientation == HORIZ)
|
||||
event->root_x = second->rect.x;
|
||||
else event->root_y = second->rect.y;
|
||||
|
|
187
src/commands.c
187
src/commands.c
|
@ -24,6 +24,14 @@
|
|||
y(bool, success); \
|
||||
y(map_close); \
|
||||
} while (0)
|
||||
#define yerror(message) do { \
|
||||
y(map_open); \
|
||||
ystr("success"); \
|
||||
y(bool, false); \
|
||||
ystr("error"); \
|
||||
ystr(message); \
|
||||
y(map_close); \
|
||||
} while (0)
|
||||
|
||||
/** When the command did not include match criteria (!), we use the currently
|
||||
* focused container. Do not confuse this case with a command which included
|
||||
|
@ -69,6 +77,17 @@ static Output *get_output_from_string(Output *current_output, const char *output
|
|||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the output containing the given container.
|
||||
*/
|
||||
static Output *get_output_of_con(Con *con) {
|
||||
Con *output_con = con_get_output(con);
|
||||
Output *output = get_output_by_name(output_con->name);
|
||||
assert(output != NULL);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether we switched to a new workspace and returns false in that case,
|
||||
* signaling that further workspace switching should be done by the calling function
|
||||
|
@ -441,12 +460,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
|
|||
ws = workspace_back_and_forth_get();
|
||||
|
||||
if (ws == NULL) {
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
ystr("No workspace was previously active.");
|
||||
y(map_close);
|
||||
yerror("No workspace was previously active.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -535,13 +549,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
|
|||
parsed_num < 0 ||
|
||||
endptr == which) {
|
||||
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
// TODO: better error message
|
||||
ystr("Could not parse number");
|
||||
y(map_close);
|
||||
yerror("Could not parse number");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -618,79 +627,50 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
|
|||
|
||||
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
|
||||
LOG("tiling resize\n");
|
||||
/* get the appropriate current container (skip stacked/tabbed cons) */
|
||||
Con *other = NULL;
|
||||
double percentage = 0;
|
||||
while (current->parent->layout == L_STACKED ||
|
||||
current->parent->layout == L_TABBED)
|
||||
current = current->parent;
|
||||
Con *second = NULL;
|
||||
Con *first = current;
|
||||
direction_t search_direction;
|
||||
if (!strcmp(direction, "left"))
|
||||
search_direction = D_LEFT;
|
||||
else if (!strcmp(direction, "right"))
|
||||
search_direction = D_RIGHT;
|
||||
else if (!strcmp(direction, "up"))
|
||||
search_direction = D_UP;
|
||||
else
|
||||
search_direction = D_DOWN;
|
||||
|
||||
/* Then further go up until we find one with the matching orientation. */
|
||||
orientation_t search_orientation =
|
||||
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
|
||||
|
||||
do {
|
||||
if (con_orientation(current->parent) != search_orientation) {
|
||||
current = current->parent;
|
||||
continue;
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction);
|
||||
if (!res) {
|
||||
LOG("No second container in this direction found.\n");
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get the default percentage */
|
||||
int children = con_num_children(current->parent);
|
||||
int children = con_num_children(first->parent);
|
||||
LOG("ins. %d children\n", children);
|
||||
percentage = 1.0 / children;
|
||||
double percentage = 1.0 / children;
|
||||
LOG("default percentage = %f\n", percentage);
|
||||
|
||||
orientation_t orientation = con_orientation(current->parent);
|
||||
|
||||
if ((orientation == HORIZ &&
|
||||
(strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
|
||||
(orientation == VERT &&
|
||||
(strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
|
||||
LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
|
||||
(orientation == HORIZ ? "horizontal" : "vertical"));
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
|
||||
other = TAILQ_PREV(current, nodes_head, nodes);
|
||||
} else {
|
||||
other = TAILQ_NEXT(current, nodes);
|
||||
}
|
||||
if (other == TAILQ_END(workspaces)) {
|
||||
LOG("No other container in this direction found, trying to look further up in the tree...\n");
|
||||
current = current->parent;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} while (current->type != CT_WORKSPACE &&
|
||||
current->type != CT_FLOATING_CON);
|
||||
|
||||
if (other == NULL) {
|
||||
LOG("No other container in this direction found, trying to look further up in the tree...\n");
|
||||
ysuccess(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG("other->percent = %f\n", other->percent);
|
||||
LOG("current->percent before = %f\n", current->percent);
|
||||
if (current->percent == 0.0)
|
||||
current->percent = percentage;
|
||||
if (other->percent == 0.0)
|
||||
other->percent = percentage;
|
||||
double new_current_percent = current->percent + ((double)ppt / 100.0);
|
||||
double new_other_percent = other->percent - ((double)ppt / 100.0);
|
||||
LOG("new_current_percent = %f\n", new_current_percent);
|
||||
LOG("new_other_percent = %f\n", new_other_percent);
|
||||
/* resize */
|
||||
LOG("second->percent = %f\n", second->percent);
|
||||
LOG("first->percent before = %f\n", first->percent);
|
||||
if (first->percent == 0.0)
|
||||
first->percent = percentage;
|
||||
if (second->percent == 0.0)
|
||||
second->percent = percentage;
|
||||
double new_first_percent = first->percent + ((double)ppt / 100.0);
|
||||
double new_second_percent = second->percent - ((double)ppt / 100.0);
|
||||
LOG("new_first_percent = %f\n", new_first_percent);
|
||||
LOG("new_second_percent = %f\n", new_second_percent);
|
||||
/* Ensure that the new percentages are positive and greater than
|
||||
* 0.05 to have a reasonable minimum size. */
|
||||
if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
|
||||
definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
|
||||
current->percent += ((double)ppt / 100.0);
|
||||
other->percent -= ((double)ppt / 100.0);
|
||||
LOG("current->percent after = %f\n", current->percent);
|
||||
LOG("other->percent after = %f\n", other->percent);
|
||||
if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
|
||||
definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
|
||||
first->percent += ((double)ppt / 100.0);
|
||||
second->percent -= ((double)ppt / 100.0);
|
||||
LOG("first->percent after = %f\n", first->percent);
|
||||
LOG("second->percent after = %f\n", second->percent);
|
||||
} else {
|
||||
LOG("Not resizing, already at minimum size\n");
|
||||
}
|
||||
|
@ -939,13 +919,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
|
|||
parsed_num < 0 ||
|
||||
endptr == which) {
|
||||
LOG("Could not parse initial part of \"%s\" as a number.\n", which);
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
// TODO: better error message
|
||||
ystr("Could not parse number");
|
||||
y(map_close);
|
||||
yerror("Could not parse number");
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1085,7 +1060,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
|
|||
|
||||
// TODO: fix the handling of criteria
|
||||
TAILQ_FOREACH(current, &owindows, owindows)
|
||||
current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
|
||||
current_output = get_output_of_con(current->con);
|
||||
|
||||
assert(current_output != NULL);
|
||||
|
||||
|
@ -1167,8 +1142,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
|
|||
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
Output *current_output = get_output_containing(current->con->rect.x,
|
||||
current->con->rect.y);
|
||||
Output *current_output = get_output_of_con(current->con);
|
||||
if (!current_output) {
|
||||
ELOG("Cannot get current output. This is a bug in i3.\n");
|
||||
ysuccess(false);
|
||||
|
@ -1439,12 +1413,7 @@ void cmd_focus(I3_CMD) {
|
|||
ELOG("You have to specify which window/container should be focused.\n");
|
||||
ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
|
||||
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
ystr("You have to specify which window/container should be focused");
|
||||
y(map_close);
|
||||
yerror("You have to specify which window/container should be focused");
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1521,7 +1490,7 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
|
|||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
printf("matching: %p / %s\n", current->con, current->con->name);
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
|
||||
}
|
||||
|
||||
|
@ -1713,7 +1682,7 @@ void cmd_focus_output(I3_CMD, char *name) {
|
|||
Output *output;
|
||||
|
||||
TAILQ_FOREACH(current, &owindows, owindows)
|
||||
current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
|
||||
current_output = get_output_of_con(current->con);
|
||||
assert(current_output != NULL);
|
||||
|
||||
output = get_output_from_string(current_output, name);
|
||||
|
@ -1750,12 +1719,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
|
|||
|
||||
if (!con_is_floating(focused)) {
|
||||
ELOG("Cannot change position. The window/container is not floating\n");
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
ystr("Cannot change position. The window/container is not floating.");
|
||||
y(map_close);
|
||||
yerror("Cannot change position. The window/container is not floating.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1790,12 +1754,7 @@ void cmd_move_window_to_center(I3_CMD, char *method) {
|
|||
|
||||
if (!con_is_floating(focused)) {
|
||||
ELOG("Cannot change position. The window/container is not floating\n");
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
ystr("Cannot change position. The window/container is not floating.");
|
||||
y(map_close);
|
||||
yerror("Cannot change position. The window/container is not floating.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1890,13 +1849,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
|
|||
if (!workspace) {
|
||||
// TODO: we should include the old workspace name here and use yajl for
|
||||
// generating the reply.
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
// TODO: better error message
|
||||
ystr("Old workspace not found");
|
||||
y(map_close);
|
||||
yerror("Old workspace not found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1908,13 +1862,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
|
|||
if (check_dest != NULL) {
|
||||
// TODO: we should include the new workspace name here and use yajl for
|
||||
// generating the reply.
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
ystr("error");
|
||||
// TODO: better error message
|
||||
ystr("New workspace already exists");
|
||||
y(map_close);
|
||||
yerror("New workspace already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1950,7 +1899,7 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
|
|||
*
|
||||
*/
|
||||
bool cmd_bar_mode(char *bar_mode, char *bar_id) {
|
||||
int mode;
|
||||
int mode = M_DOCK;
|
||||
bool toggle = false;
|
||||
if (strcmp(bar_mode, "dock") == 0)
|
||||
mode = M_DOCK;
|
||||
|
@ -1995,7 +1944,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) {
|
|||
*
|
||||
*/
|
||||
bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) {
|
||||
int hidden_state;
|
||||
int hidden_state = S_SHOW;
|
||||
bool toggle = false;
|
||||
if (strcmp(bar_hidden_state, "hide") == 0)
|
||||
hidden_state = S_HIDE;
|
||||
|
|
|
@ -569,8 +569,9 @@ void con_fix_percent(Con *con) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Toggles fullscreen mode for the given container. Fullscreen mode will not be
|
||||
* entered when there already is a fullscreen container on this workspace.
|
||||
* Toggles fullscreen mode for the given container. If there already is a
|
||||
* fullscreen container on this workspace, fullscreen will be disabled and then
|
||||
* enabled for the container the user wants to have in fullscreen mode.
|
||||
*
|
||||
*/
|
||||
void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
|
||||
|
|
|
@ -14,16 +14,6 @@
|
|||
|
||||
#include "all.h"
|
||||
|
||||
// Macros to make the YAJL API a bit easier to use.
|
||||
#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
|
||||
#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
|
||||
#define ysuccess(success) do { \
|
||||
y(map_open); \
|
||||
ystr("success"); \
|
||||
y(bool, success); \
|
||||
y(map_close); \
|
||||
} while (0)
|
||||
|
||||
/*******************************************************************************
|
||||
* Criteria functions.
|
||||
******************************************************************************/
|
||||
|
@ -550,6 +540,10 @@ CFGFUN(bar_status_command, const char *command) {
|
|||
current_bar.status_command = sstrdup(command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value) {
|
||||
current_bar.hide_binding_mode_indicator = !eval_boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_workspace_buttons, const char *value) {
|
||||
current_bar.hide_workspace_buttons = !eval_boolstr(value);
|
||||
}
|
||||
|
|
|
@ -68,11 +68,11 @@ static yajl_callbacks version_callbacks = {
|
|||
*
|
||||
*/
|
||||
void display_running_version(void) {
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen);
|
||||
if (socket_path == NULL)
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
char *pid_from_atom = root_atom_contents("I3_PID");
|
||||
char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen);
|
||||
if (pid_from_atom == NULL) {
|
||||
/* If I3_PID is not set, the running version is older than 4.2-200. */
|
||||
printf("\nRunning version: < 4.2-200\n");
|
||||
|
@ -128,13 +128,18 @@ void display_running_version(void) {
|
|||
printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
|
||||
|
||||
#ifdef __linux__
|
||||
char exepath[PATH_MAX],
|
||||
destpath[PATH_MAX];
|
||||
size_t destpath_size = 1024;
|
||||
ssize_t linksize;
|
||||
char *exepath;
|
||||
char *destpath = smalloc(destpath_size);
|
||||
|
||||
snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid());
|
||||
sasprintf(&exepath, "/proc/%d/exe", getpid());
|
||||
|
||||
if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
|
||||
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
|
||||
destpath_size = destpath_size * 2;
|
||||
destpath = srealloc(destpath, destpath_size);
|
||||
}
|
||||
if (linksize == -1)
|
||||
err(EXIT_FAILURE, "readlink(%s)", exepath);
|
||||
|
||||
/* readlink() does not NULL-terminate strings, so we have to. */
|
||||
|
@ -143,9 +148,14 @@ void display_running_version(void) {
|
|||
printf("\n");
|
||||
printf("The i3 binary you just called: %s\n", destpath);
|
||||
|
||||
snprintf(exepath, sizeof(exepath), "/proc/%s/exe", pid_from_atom);
|
||||
free(exepath);
|
||||
sasprintf(&exepath, "/proc/%s/exe", pid_from_atom);
|
||||
|
||||
if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1)
|
||||
while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) {
|
||||
destpath_size = destpath_size * 2;
|
||||
destpath = srealloc(destpath, destpath_size);
|
||||
}
|
||||
if (linksize == -1)
|
||||
err(EXIT_FAILURE, "readlink(%s)", exepath);
|
||||
|
||||
/* readlink() does not NULL-terminate strings, so we have to. */
|
||||
|
@ -159,7 +169,8 @@ void display_running_version(void) {
|
|||
/* Since readlink() might put a "(deleted)" somewhere in the buffer and
|
||||
* stripping that out seems hackish and ugly, we read the process’s argv[0]
|
||||
* instead. */
|
||||
snprintf(exepath, sizeof(exepath), "/proc/%s/cmdline", pid_from_atom);
|
||||
free(exepath);
|
||||
sasprintf(&exepath, "/proc/%s/cmdline", pid_from_atom);
|
||||
|
||||
int fd;
|
||||
if ((fd = open(exepath, O_RDONLY)) == -1)
|
||||
|
@ -169,6 +180,9 @@ void display_running_version(void) {
|
|||
close(fd);
|
||||
|
||||
printf("The i3 binary you are running: %s\n", destpath);
|
||||
|
||||
free(exepath);
|
||||
free(destpath);
|
||||
#endif
|
||||
|
||||
yajl_free(handle);
|
||||
|
|
|
@ -441,8 +441,15 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
|
|||
* after the user releases the mouse button */
|
||||
tree_render();
|
||||
|
||||
/* Store the initial rect in case of user revert/cancel */
|
||||
Rect initial_rect = con->rect;
|
||||
|
||||
/* Drag the window */
|
||||
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
|
||||
|
||||
/* If the user cancelled, undo the changes. */
|
||||
if (drag_result == DRAG_REVERT)
|
||||
floating_reposition(con, initial_rect);
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
|
@ -546,7 +553,14 @@ void floating_resize_window(Con *con, const bool proportional,
|
|||
|
||||
struct resize_window_callback_params params = { corner, proportional, event };
|
||||
|
||||
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms);
|
||||
/* get the initial rect in case of revert/cancel */
|
||||
Rect initial_rect = con->rect;
|
||||
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms);
|
||||
|
||||
/* If the user cancels, undo the resize */
|
||||
if (drag_result == DRAG_REVERT)
|
||||
floating_reposition(con, initial_rect);
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
|
@ -554,14 +568,14 @@ void floating_resize_window(Con *con, const bool proportional,
|
|||
}
|
||||
|
||||
/*
|
||||
* This function grabs your pointer and lets you drag stuff around (borders).
|
||||
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received
|
||||
* and the given callback will be called with the parameters specified (client,
|
||||
* border on which the click originally was), the original rect of the client,
|
||||
* the event and the new coordinates (x, y).
|
||||
* This function grabs your pointer and keyboard and lets you drag stuff around
|
||||
* (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
|
||||
* be received and the given callback will be called with the parameters
|
||||
* specified (client, border on which the click originally was), the original
|
||||
* rect of the client, the event and the new coordinates (x, y).
|
||||
*
|
||||
*/
|
||||
void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
||||
drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
||||
confine_to, border_t border, int cursor, callback_t callback, const void *extra)
|
||||
{
|
||||
uint32_t new_x, new_y;
|
||||
|
@ -569,7 +583,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
if (con != NULL)
|
||||
memcpy(&old_rect, &(con->rect), sizeof(Rect));
|
||||
|
||||
Cursor xcursor = (cursor && xcursor_supported) ?
|
||||
xcb_cursor_t xcursor = (cursor && xcursor_supported) ?
|
||||
xcursor_get_cursor(cursor) : XCB_NONE;
|
||||
|
||||
/* Grab the pointer */
|
||||
|
@ -587,18 +601,39 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
|
||||
if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
|
||||
ELOG("Could not grab pointer\n");
|
||||
return;
|
||||
return DRAG_ABORT;
|
||||
}
|
||||
|
||||
free(reply);
|
||||
|
||||
/* Grab the keyboard */
|
||||
xcb_grab_keyboard_cookie_t keyb_cookie;
|
||||
xcb_grab_keyboard_reply_t *keyb_reply;
|
||||
|
||||
keyb_cookie = xcb_grab_keyboard(conn,
|
||||
false, /* get all keyboard events */
|
||||
root, /* grab the root window */
|
||||
XCB_CURRENT_TIME,
|
||||
XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
|
||||
XCB_GRAB_MODE_ASYNC /* keyboard mode */
|
||||
);
|
||||
|
||||
if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
|
||||
ELOG("Could not grab keyboard\n");
|
||||
return DRAG_ABORT;
|
||||
}
|
||||
|
||||
free(keyb_reply);
|
||||
|
||||
/* Go into our own event loop */
|
||||
xcb_flush(conn);
|
||||
|
||||
xcb_generic_event_t *inside_event, *last_motion_notify = NULL;
|
||||
bool loop_done = false;
|
||||
Con *inside_con = NULL;
|
||||
|
||||
drag_result_t drag_result = DRAGGING;
|
||||
/* I’ve always wanted to have my own eventhandler… */
|
||||
while (!loop_done && (inside_event = xcb_wait_for_event(conn))) {
|
||||
while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) {
|
||||
/* We now handle all events we can get using xcb_poll_for_event */
|
||||
do {
|
||||
/* skip x11 errors */
|
||||
|
@ -611,7 +646,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
|
||||
switch (type) {
|
||||
case XCB_BUTTON_RELEASE:
|
||||
loop_done = true;
|
||||
drag_result = DRAG_SUCCESS;
|
||||
break;
|
||||
|
||||
case XCB_MOTION_NOTIFY:
|
||||
|
@ -621,11 +656,26 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
break;
|
||||
|
||||
case XCB_UNMAP_NOTIFY:
|
||||
case XCB_KEY_PRESS:
|
||||
case XCB_KEY_RELEASE:
|
||||
DLOG("Unmap-notify, aborting\n");
|
||||
inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window);
|
||||
|
||||
if (inside_con != NULL) {
|
||||
DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con);
|
||||
|
||||
if (con_get_workspace(inside_con) == con_get_workspace(focused)) {
|
||||
DLOG("UnmapNotify for a managed window on the current workspace, aborting\n");
|
||||
drag_result = DRAG_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
handle_event(type, inside_event);
|
||||
break;
|
||||
|
||||
case XCB_KEY_PRESS:
|
||||
/* Cancel the drag if a key was pressed */
|
||||
DLOG("A key was pressed during drag, reverting changes.");
|
||||
drag_result = DRAG_REVERT;
|
||||
|
||||
handle_event(type, inside_event);
|
||||
loop_done = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -638,7 +688,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
free(inside_event);
|
||||
} while ((inside_event = xcb_poll_for_event(conn)) != NULL);
|
||||
|
||||
if (last_motion_notify == NULL || loop_done)
|
||||
if (last_motion_notify == NULL || drag_result != DRAGGING)
|
||||
continue;
|
||||
|
||||
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
|
||||
|
@ -648,8 +698,12 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
|
|||
FREE(last_motion_notify);
|
||||
}
|
||||
|
||||
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
|
||||
xcb_flush(conn);
|
||||
|
||||
return drag_result;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -54,12 +54,12 @@ src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stam
|
|||
|
||||
i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec
|
||||
echo "[i3] Generating command parser"
|
||||
(cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
|
||||
(cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command)
|
||||
touch $@
|
||||
|
||||
i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec
|
||||
echo "[i3] Generating config parser"
|
||||
(cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
|
||||
(cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config)
|
||||
touch $@
|
||||
|
||||
i3: libi3.a $(i3_OBJECTS)
|
||||
|
|
|
@ -686,6 +686,9 @@ IPC_HANDLER(get_bar_config) {
|
|||
ystr("workspace_buttons");
|
||||
y(bool, !config->hide_workspace_buttons);
|
||||
|
||||
ystr("binding_mode_indicator");
|
||||
y(bool, !config->hide_binding_mode_indicator);
|
||||
|
||||
ystr("verbose");
|
||||
y(bool, config->verbose);
|
||||
|
||||
|
|
|
@ -129,11 +129,16 @@ void open_logbuffer(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
|
||||
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
|
||||
#else
|
||||
int ret;
|
||||
if ((ret = posix_fallocate(logbuffer_shm, 0, logbuffer_size)) != 0) {
|
||||
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret));
|
||||
#endif
|
||||
close(logbuffer_shm);
|
||||
shm_unlink(shmlogname);
|
||||
fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
40
src/main.c
40
src/main.c
|
@ -352,7 +352,7 @@ int main(int argc, char *argv[]) {
|
|||
break;
|
||||
} else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 ||
|
||||
strcmp(long_options[option_index].name, "get_socketpath") == 0) {
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
|
||||
if (socket_path) {
|
||||
printf("%s\n", socket_path);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
@ -442,7 +442,7 @@ int main(int argc, char *argv[]) {
|
|||
optind++;
|
||||
}
|
||||
DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload));
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH");
|
||||
char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0);
|
||||
if (!socket_path) {
|
||||
ELOG("Could not get i3 IPC socket path\n");
|
||||
return 1;
|
||||
|
@ -488,18 +488,25 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
/* The following code is helpful, but not required. We thus don’t pay
|
||||
* much attention to error handling, non-linux or other edge cases. */
|
||||
char cwd[PATH_MAX];
|
||||
LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n");
|
||||
if (getcwd(cwd, sizeof(cwd)) != NULL)
|
||||
size_t cwd_size = 1024;
|
||||
char *cwd = smalloc(cwd_size);
|
||||
char *cwd_ret;
|
||||
while ((cwd_ret = getcwd(cwd, cwd_size)) == NULL && errno == ERANGE) {
|
||||
cwd_size = cwd_size * 2;
|
||||
cwd = srealloc(cwd, cwd_size);
|
||||
}
|
||||
if (cwd_ret != NULL)
|
||||
LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd);
|
||||
int patternfd;
|
||||
if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) {
|
||||
memset(cwd, '\0', sizeof(cwd));
|
||||
if (read(patternfd, cwd, sizeof(cwd)) > 0)
|
||||
memset(cwd, '\0', cwd_size);
|
||||
if (read(patternfd, cwd, cwd_size) > 0)
|
||||
/* a trailing newline is included in cwd */
|
||||
LOG("CORE DUMPS: Your core_pattern is: %s", cwd);
|
||||
close(patternfd);
|
||||
}
|
||||
free(cwd);
|
||||
}
|
||||
|
||||
LOG("i3 " I3_VERSION " starting\n");
|
||||
|
@ -794,6 +801,27 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
xcb_ungrab_server(conn);
|
||||
|
||||
if (autostart) {
|
||||
LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
|
||||
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
|
||||
uint16_t width = root->width_in_pixels;
|
||||
uint16_t height = root->height_in_pixels;
|
||||
xcb_pixmap_t pixmap = xcb_generate_id(conn);
|
||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
||||
|
||||
xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
|
||||
|
||||
xcb_create_gc(conn, gc, root->root,
|
||||
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
|
||||
(uint32_t[]){ XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS });
|
||||
|
||||
xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
|
||||
xcb_change_window_attributes_checked(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){ pixmap });
|
||||
xcb_flush(conn);
|
||||
xcb_free_gc(conn, gc);
|
||||
xcb_free_pixmap(conn, pixmap);
|
||||
}
|
||||
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_signal;
|
||||
|
|
27
src/manage.c
27
src/manage.c
|
@ -279,11 +279,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) {
|
||||
DLOG("Assignment matches (%p)\n", match);
|
||||
if (assignment->type == A_TO_WORKSPACE) {
|
||||
nc = con_descend_tiling_focused(workspace_get(assignment->dest.workspace, NULL));
|
||||
DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name);
|
||||
Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL);
|
||||
nc = con_descend_tiling_focused(assigned_ws);
|
||||
DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name);
|
||||
if (nc->type == CT_WORKSPACE)
|
||||
nc = tree_open_con(nc, cwindow);
|
||||
else nc = tree_open_con(nc->parent, cwindow);
|
||||
else
|
||||
nc = tree_open_con(nc->parent, cwindow);
|
||||
|
||||
/* set the urgency hint on the window if the workspace is not visible */
|
||||
if (!workspace_is_visible(assigned_ws))
|
||||
urgency_hint = true;
|
||||
}
|
||||
/* TODO: handle assignments with type == A_TO_OUTPUT */
|
||||
} else if (startup_ws) {
|
||||
|
@ -322,11 +328,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
x_set_name(nc, name);
|
||||
free(name);
|
||||
|
||||
/* handle fullscreen containers */
|
||||
Con *ws = con_get_workspace(nc);
|
||||
Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL);
|
||||
if (fs == NULL)
|
||||
fs = con_get_fullscreen_con(croot, CF_GLOBAL);
|
||||
|
||||
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
|
||||
if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) {
|
||||
fs = NULL;
|
||||
con_toggle_fullscreen(nc, CF_OUTPUT);
|
||||
}
|
||||
|
||||
FREE(state_reply);
|
||||
|
||||
if (fs == NULL) {
|
||||
DLOG("Not in fullscreen mode, focusing\n");
|
||||
if (!cwindow->dock) {
|
||||
|
@ -430,12 +445,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
|||
xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values);
|
||||
xcb_flush(conn);
|
||||
|
||||
reply = xcb_get_property_reply(conn, state_cookie, NULL);
|
||||
if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN))
|
||||
con_toggle_fullscreen(nc, CF_OUTPUT);
|
||||
|
||||
FREE(reply);
|
||||
|
||||
/* Put the client inside the save set. Upon termination (whether killed or
|
||||
* normal exit does not matter) of the window manager, these clients will
|
||||
* be correctly reparented to their most closest living ancestor (=
|
||||
|
|
60
src/move.c
60
src/move.c
|
@ -65,11 +65,12 @@ static void insert_con_into(Con *con, Con *target, position_t position) {
|
|||
}
|
||||
|
||||
/*
|
||||
* This function detaches 'con' from its parent and inserts it at the given
|
||||
* workspace.
|
||||
* This function detaches 'con' from its parent and puts it in the given
|
||||
* workspace. Position is determined by the direction of movement into the
|
||||
* workspace container.
|
||||
*
|
||||
*/
|
||||
static void attach_to_workspace(Con *con, Con *ws) {
|
||||
static void attach_to_workspace(Con *con, Con *ws, direction_t direction) {
|
||||
con_detach(con);
|
||||
con_fix_percent(con->parent);
|
||||
|
||||
|
@ -77,8 +78,13 @@ static void attach_to_workspace(Con *con, Con *ws) {
|
|||
|
||||
con->parent = ws;
|
||||
|
||||
if (direction == D_RIGHT || direction == D_DOWN) {
|
||||
TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes);
|
||||
TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused);
|
||||
} else {
|
||||
TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes);
|
||||
TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused);
|
||||
}
|
||||
|
||||
/* Pretend the con was just opened with regards to size percent values.
|
||||
* Since the con is moved to a completely different con, the old value
|
||||
|
@ -87,6 +93,32 @@ static void attach_to_workspace(Con *con, Con *ws) {
|
|||
con_fix_percent(ws);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container to the closest output in the given direction if
|
||||
* such an output exists.
|
||||
*
|
||||
*/
|
||||
static void move_to_output_directed(Con *con, direction_t direction) {
|
||||
Con *current_output_con = con_get_output(con);
|
||||
Output *current_output = get_output_by_name(current_output_con->name);
|
||||
Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
|
||||
|
||||
if (!output) {
|
||||
DLOG("No output in this direction found. Not moving.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||
|
||||
if (!ws) {
|
||||
DLOG("No workspace on output in this direction found. Not moving.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
attach_to_workspace(con, ws, direction);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the current container in the given direction (D_LEFT, D_RIGHT,
|
||||
* D_UP, D_DOWN).
|
||||
|
@ -103,8 +135,9 @@ void tree_move(int direction) {
|
|||
}
|
||||
|
||||
if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) {
|
||||
DLOG("This is the only con on this workspace, not doing anything\n");
|
||||
return;
|
||||
/* This is the only con on this workspace */
|
||||
move_to_output_directed(con, direction);
|
||||
goto end;
|
||||
}
|
||||
|
||||
orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT);
|
||||
|
@ -124,7 +157,7 @@ void tree_move(int direction) {
|
|||
if (con_inside_floating(con)) {
|
||||
/* 'con' should be moved out of a floating container */
|
||||
DLOG("Inside floating, moving to workspace\n");
|
||||
attach_to_workspace(con, con_get_workspace(con));
|
||||
attach_to_workspace(con, con_get_workspace(con), direction);
|
||||
goto end;
|
||||
}
|
||||
DLOG("Force-changing orientation\n");
|
||||
|
@ -154,12 +187,15 @@ void tree_move(int direction) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* If there was no con with which we could swap the current one, search
|
||||
* again, but starting one level higher. If we are on the workspace
|
||||
* level, don’t do that. The result would be a force change of
|
||||
* workspace orientation, which is not necessary. */
|
||||
if (con->parent == con_get_workspace(con))
|
||||
return;
|
||||
if (con->parent == con_get_workspace(con)) {
|
||||
/* If we couldn't find a place to move it on this workspace,
|
||||
* try to move it to a workspace on a different output */
|
||||
move_to_output_directed(con, direction);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* If there was no con with which we could swap the current one,
|
||||
* search again, but starting one level higher. */
|
||||
same_orientation = con_parent_with_orientation(con->parent, o);
|
||||
}
|
||||
} while (same_orientation == NULL);
|
||||
|
|
54
src/resize.c
54
src/resize.c
|
@ -51,6 +51,54 @@ DRAGGING_CB(resize_callback) {
|
|||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) {
|
||||
DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction);
|
||||
Con *first = *current;
|
||||
Con *second = NULL;
|
||||
if (first == NULL) {
|
||||
DLOG("Current container is NULL, aborting.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Go up in the tree and search for a container to resize */
|
||||
const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT);
|
||||
const bool dir_backwards = (direction == D_UP || direction == D_LEFT);
|
||||
while (first->type != CT_WORKSPACE &&
|
||||
first->type != CT_FLOATING_CON &&
|
||||
second == NULL) {
|
||||
/* get the appropriate first container with the matching
|
||||
* orientation (skip stacked/tabbed cons) */
|
||||
if ((con_orientation(first->parent) != search_orientation) ||
|
||||
(first->parent->layout == L_STACKED) ||
|
||||
(first->parent->layout == L_TABBED)) {
|
||||
first = first->parent;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get the counterpart for this resizement */
|
||||
if (dir_backwards) {
|
||||
second = TAILQ_PREV(first, nodes_head, nodes);
|
||||
} else {
|
||||
second = TAILQ_NEXT(first, nodes);
|
||||
}
|
||||
|
||||
if (second == NULL) {
|
||||
DLOG("No second container in this direction found, trying to look further up in the tree...\n");
|
||||
first = first->parent;
|
||||
}
|
||||
}
|
||||
|
||||
DLOG("Found participants: first=%p and second=%p.", first, second);
|
||||
*current = first;
|
||||
*other = second;
|
||||
if (first == NULL || second == NULL) {
|
||||
DLOG("Could not find two participants for this resize request.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) {
|
||||
DLOG("resize handler\n");
|
||||
|
||||
|
@ -106,12 +154,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation,
|
|||
|
||||
const struct callback_params params = { orientation, output, helpwin, &new_position };
|
||||
|
||||
drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms);
|
||||
drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms);
|
||||
|
||||
xcb_destroy_window(conn, helpwin);
|
||||
xcb_destroy_window(conn, grabwin);
|
||||
xcb_flush(conn);
|
||||
|
||||
/* User cancelled the drag so no action should be taken. */
|
||||
if (drag_result == DRAG_REVERT)
|
||||
return 0;
|
||||
|
||||
int pixels;
|
||||
if (orientation == HORIZ)
|
||||
pixels = (new_position - event->root_x);
|
||||
|
|
|
@ -66,8 +66,14 @@ void scratchpad_move(Con *con) {
|
|||
* adjusted in size according to what the user specifies. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_NONE) {
|
||||
DLOG("This window was never used as a scratchpad before.\n");
|
||||
if (con == maybe_floating_con) {
|
||||
DLOG("It was in floating mode before, set scratchpad state to changed.\n");
|
||||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
} else {
|
||||
DLOG("It was in tiling mode before, set scratchpad state to fresh.\n");
|
||||
con->scratchpad_state = SCRATCHPAD_FRESH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <paths.h>
|
||||
|
||||
#define SN_API_NOT_YET_FROZEN 1
|
||||
#include <libsn/sn-launcher.h>
|
||||
|
@ -191,15 +192,7 @@ void start_application(const char *command, bool no_startup_id) {
|
|||
if (!no_startup_id)
|
||||
sn_launcher_context_setup_child_process(context);
|
||||
|
||||
/* Stores the path of the shell */
|
||||
static const char *shell = NULL;
|
||||
|
||||
if (shell == NULL)
|
||||
if ((shell = getenv("SHELL")) == NULL)
|
||||
shell = "/bin/sh";
|
||||
|
||||
/* This is the child */
|
||||
execl(shell, shell, "-c", command, (void*)NULL);
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL);
|
||||
/* not reached */
|
||||
}
|
||||
_exit(0);
|
||||
|
|
17
src/tree.c
17
src/tree.c
|
@ -359,15 +359,24 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
|
|||
*/
|
||||
void tree_close_con(kill_window_t kill_window) {
|
||||
assert(focused != NULL);
|
||||
if (focused->type == CT_WORKSPACE) {
|
||||
LOG("Cannot close workspace\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* There *should* be no possibility to focus outputs / root container */
|
||||
assert(focused->type != CT_OUTPUT);
|
||||
assert(focused->type != CT_ROOT);
|
||||
|
||||
if (focused->type == CT_WORKSPACE) {
|
||||
DLOG("Workspaces cannot be close, closing all children instead\n");
|
||||
Con *child, *nextchild;
|
||||
for (child = TAILQ_FIRST(&(focused->focus_head)); child; ) {
|
||||
nextchild = TAILQ_NEXT(child, focused);
|
||||
DLOG("killing child=%p\n", child);
|
||||
tree_close(child, kill_window, false, false);
|
||||
child = nextchild;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Kill con */
|
||||
tree_close(focused, kill_window, false, false);
|
||||
}
|
||||
|
|
|
@ -197,17 +197,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
|||
while (exists) {
|
||||
c++;
|
||||
|
||||
FREE(ws->name);
|
||||
sasprintf(&(ws->name), "%d", c);
|
||||
ws->num = c;
|
||||
|
||||
current = NULL;
|
||||
TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
|
||||
GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
|
||||
GREP_FIRST(current, output_get_content(out), child->num == ws->num);
|
||||
exists = (current != NULL);
|
||||
|
||||
DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
|
||||
DLOG("result for ws %d: exists = %d\n", c, exists);
|
||||
}
|
||||
ws->num = c;
|
||||
sasprintf(&(ws->name), "%d", c);
|
||||
}
|
||||
con_attach(ws, content, false);
|
||||
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* xcursor.c: libXcursor support for themed cursors.
|
||||
* xcursor.c: xcursor support for themed cursors.
|
||||
*
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
|
||||
#include "i3.h"
|
||||
#include "xcb.h"
|
||||
#include "xcursor.h"
|
||||
|
||||
static Cursor cursors[XCURSOR_CURSOR_MAX];
|
||||
static xcb_cursor_context_t *ctx;
|
||||
static xcb_cursor_t cursors[XCURSOR_CURSOR_MAX];
|
||||
|
||||
static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
|
||||
XCB_CURSOR_LEFT_PTR,
|
||||
|
@ -26,23 +26,26 @@ static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
|
|||
XCB_CURSOR_WATCH
|
||||
};
|
||||
|
||||
static Cursor load_cursor(const char *name) {
|
||||
Cursor c = XcursorLibraryLoadCursor(xlibdpy, name);
|
||||
if (c == None)
|
||||
xcursor_supported = false;
|
||||
return c;
|
||||
}
|
||||
|
||||
void xcursor_load_cursors(void) {
|
||||
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
|
||||
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
|
||||
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
|
||||
cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
|
||||
cursors[XCURSOR_CURSOR_MOVE] = load_cursor("fleur");
|
||||
cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER] = load_cursor("top_left_corner");
|
||||
cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER] = load_cursor("top_right_corner");
|
||||
cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER] = load_cursor("bottom_left_corner");
|
||||
cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner");
|
||||
if (xcb_cursor_context_new(conn, root_screen, &ctx) < 0) {
|
||||
ELOG("xcursor support unavailable\n");
|
||||
xcursor_supported = false;
|
||||
return;
|
||||
}
|
||||
#define LOAD_CURSOR(constant, name) \
|
||||
do { \
|
||||
cursors[constant] = xcb_cursor_load_cursor(ctx, name); \
|
||||
} while (0)
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_POINTER, "left_ptr");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_HORIZONTAL, "sb_h_double_arrow");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_VERTICAL, "sb_v_double_arrow");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_WATCH, "watch");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_MOVE, "fleur");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_TOP_LEFT_CORNER, "top_left_corner");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_TOP_RIGHT_CORNER, "top_right_corner");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_LEFT_CORNER, "bottom_left_corner");
|
||||
LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER, "bottom_right_corner");
|
||||
#undef LOAD_CURSOR
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -51,19 +54,13 @@ void xcursor_load_cursors(void) {
|
|||
* This function is called when i3 is initialized, because with some login
|
||||
* managers, the root window will not have a cursor otherwise.
|
||||
*
|
||||
* We have a separate xcursor function to use the same X11 connection as the
|
||||
* xcursor_load_cursors() function. If we mix the Xlib and the XCB connection,
|
||||
* races might occur (even though we flush the Xlib connection).
|
||||
*
|
||||
*/
|
||||
void xcursor_set_root_cursor(int cursor_id) {
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.cursor = xcursor_get_cursor(cursor_id);
|
||||
XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
|
||||
XFlush(xlibdpy);
|
||||
xcb_change_window_attributes(conn, root, XCB_CW_CURSOR,
|
||||
(uint32_t[]){ xcursor_get_cursor(cursor_id) });
|
||||
}
|
||||
|
||||
Cursor xcursor_get_cursor(enum xcursor_cursor_t c) {
|
||||
xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) {
|
||||
assert(c >= 0 && c < XCURSOR_CURSOR_MAX);
|
||||
return cursors[c];
|
||||
}
|
||||
|
|
|
@ -8,3 +8,4 @@ inc
|
|||
META.yml
|
||||
i3-cfg-for-*
|
||||
-
|
||||
Xdummy.so
|
||||
|
|
|
@ -8,8 +8,8 @@ WriteMakefile(
|
|||
MIN_PERL_VERSION => '5.010000', # 5.10.0
|
||||
PREREQ_PM => {
|
||||
'AnyEvent' => 0,
|
||||
'AnyEvent::I3' => '0.14',
|
||||
'X11::XCB' => '0.03',
|
||||
'AnyEvent::I3' => '0.15',
|
||||
'X11::XCB' => '0.09',
|
||||
'Inline' => 0,
|
||||
'ExtUtils::PkgConfig' => 0,
|
||||
'Test::More' => '0.94',
|
||||
|
|
|
@ -108,7 +108,7 @@ $outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime());
|
|||
$outdir .= `git describe --tags`;
|
||||
chomp($outdir);
|
||||
mkdir($outdir) or die "Could not create $outdir";
|
||||
unlink("latest") if -e "latest";
|
||||
unlink("latest") if -l "latest";
|
||||
symlink("$outdir", "latest") or die "Could not symlink latest to $outdir";
|
||||
|
||||
|
||||
|
@ -143,7 +143,7 @@ my $timingsjson = StartXDummy::slurp('.last_run_timings.json');
|
|||
|
||||
# Run 000-load-deps.t first to bail out early when dependencies are missing.
|
||||
my $loadtest = "t/000-load-deps.t";
|
||||
if ($loadtest ~~ @testfiles) {
|
||||
if ((scalar grep { $_ eq $loadtest } @testfiles) > 0) {
|
||||
@testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles);
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ sub start_xdummy {
|
|||
# actual system X configuration.
|
||||
my $socket = fork_xserver($keep_xdummy_output, $displaynum,
|
||||
'./Xdummy', ":$displaynum", '-config', '/dev/null',
|
||||
'-nolisten', 'tcp');
|
||||
'-configdir', '/dev/null', '-nolisten', 'tcp');
|
||||
push(@displays, ":$displaynum");
|
||||
push(@sockets_waiting, $socket);
|
||||
$displaynum++;
|
||||
|
|
|
@ -155,6 +155,9 @@ __
|
|||
warnings->import;
|
||||
|
||||
$x ||= i3test::X11->new;
|
||||
# set the pointer to a predictable position in case a previous test has
|
||||
# disturbed it
|
||||
$x->root->warp_pointer(0, 0);
|
||||
$cv->recv if $i3_autostart;
|
||||
|
||||
@_ = ($class);
|
||||
|
@ -406,7 +409,7 @@ C<fresh_workspace> which directly switches to an unused workspace.
|
|||
sub get_unused_workspace {
|
||||
my @names = get_workspace_names();
|
||||
my $tmp;
|
||||
do { $tmp = tmpnam() } while ($tmp ~~ @names);
|
||||
do { $tmp = tmpnam() } while ((scalar grep { $_ eq $tmp } @names) > 0);
|
||||
$tmp
|
||||
}
|
||||
|
||||
|
@ -626,7 +629,7 @@ Returns true if C<$workspace> is the name of an existing workspace.
|
|||
=cut
|
||||
sub workspace_exists {
|
||||
my ($name) = @_;
|
||||
($name ~~ @{get_workspace_names()})
|
||||
(scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
|
||||
}
|
||||
|
||||
=head2 focused_ws
|
||||
|
|
|
@ -15,6 +15,16 @@ use File::Basename qw(basename);
|
|||
use Getopt::Long;
|
||||
use v5.10;
|
||||
|
||||
my $usage = <<'EOF';
|
||||
Script to create a new testcase from a template.
|
||||
|
||||
# Create (and edit) a new test for moving floating windows
|
||||
./new-test floating move
|
||||
|
||||
# Create (and edit) a multi-monitor test for moving workspaces
|
||||
./new-test -m move workspaces
|
||||
EOF
|
||||
|
||||
my $multi_monitor;
|
||||
|
||||
my $result = GetOptions(
|
||||
|
@ -24,6 +34,11 @@ my $result = GetOptions(
|
|||
my $testname = join(' ', @ARGV);
|
||||
$testname =~ s/ /-/g;
|
||||
|
||||
unless (length $testname) {
|
||||
say $usage;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
my $header = <<'EOF';
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
@ -68,10 +83,6 @@ if ($multi_monitor) {
|
|||
print $fh <<'EOF';
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# Ensure the pointer is at (0, 0) so that we really start on the first
|
||||
# (the left) workspace.
|
||||
$x->root->warp_pointer(0, 0);
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
|
|
@ -214,4 +214,25 @@ sync_with_i3;
|
|||
# Verify that $swindow was the one that initially remained fullscreen.
|
||||
is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws');
|
||||
|
||||
################################################################################
|
||||
# Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any
|
||||
# existing container on the workspace and fullscreens the newly opened window.
|
||||
################################################################################
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
$window = open_window();
|
||||
|
||||
cmd "fullscreen";
|
||||
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
|
||||
is($x->input_focus, $window->id, 'fullscreen window focused');
|
||||
|
||||
$swindow = open_window({
|
||||
fullscreen => 1
|
||||
});
|
||||
|
||||
is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
|
||||
is($x->input_focus, $swindow->id, 'fullscreen window focused');
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -24,8 +24,11 @@ my $_NET_WM_STATE_TOGGLE = 2;
|
|||
sub set_urgency {
|
||||
my ($win, $urgent_flag, $type) = @_;
|
||||
if ($type == 1) {
|
||||
# Because X11::XCB does not keep track of clearing the urgency hint
|
||||
# when receiving focus, we just delete it in all cases and then re-set
|
||||
# it if appropriate.
|
||||
$win->delete_hint('urgency');
|
||||
$win->add_hint('urgency') if ($urgent_flag);
|
||||
$win->delete_hint('urgency') if (!$urgent_flag);
|
||||
} elsif ($type == 2) {
|
||||
my $msg = pack "CCSLLLLLL",
|
||||
X11::XCB::CLIENT_MESSAGE, # response_type
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
# Tests all kinds of matching methods
|
||||
#
|
||||
use i3test;
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
|
@ -61,39 +60,10 @@ is_num_children($tmp, 0, 'window killed');
|
|||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
|
||||
return open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
);
|
||||
}
|
||||
|
||||
my $left = open_special(name => 'left');
|
||||
my $left = open_window(wm_class => 'special', name => 'left');
|
||||
ok($left->mapped, 'left window mapped');
|
||||
|
||||
my $right = open_special(name => 'right');
|
||||
my $right = open_window(wm_class => 'special', name => 'right');
|
||||
ok($right->mapped, 'right window mapped');
|
||||
|
||||
# two windows should be here
|
||||
|
@ -111,7 +81,7 @@ is_num_children($tmp, 1, 'one window still there');
|
|||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
$left = open_special(name => 'left', wm_class => 'special7');
|
||||
$left = open_window(name => 'left', wm_class => 'special7');
|
||||
ok($left->mapped, 'left window mapped');
|
||||
is_num_children($tmp, 1, 'window opened');
|
||||
|
||||
|
@ -125,7 +95,7 @@ is_num_children($tmp, 0, 'window killed');
|
|||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
$left = open_special(name => 'ä 3', wm_class => 'special7');
|
||||
$left = open_window(name => 'ä 3', wm_class => 'special7');
|
||||
ok($left->mapped, 'left window mapped');
|
||||
is_num_children($tmp, 1, 'window opened');
|
||||
|
||||
|
|
|
@ -119,6 +119,55 @@ sync_with_i3;
|
|||
|
||||
is(get_focused($tmp), $middle, 'middle container focused');
|
||||
|
||||
##############################################################
|
||||
# check if the workspace container can be closed
|
||||
##############################################################
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
my $window = open_window();
|
||||
|
||||
# one window opened on the current workspace
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$nodes, 1, 'workspace contains one node');
|
||||
|
||||
# focus the workspace
|
||||
cmd "focus parent";
|
||||
cmd "focus parent";
|
||||
|
||||
# try to kill the workspace
|
||||
cmd "kill";
|
||||
sync_with_i3;
|
||||
|
||||
# the workspace should now be empty
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$nodes, 0, 'workspace is empty');
|
||||
|
||||
################################################################################
|
||||
# check if killing a workspace also closes floating windows.
|
||||
################################################################################
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
$window = open_window;
|
||||
my $floating_window = open_floating_window;
|
||||
|
||||
# one window opened on the current workspace
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$focus, 2, 'workspace contains two nodes');
|
||||
|
||||
# focus the workspace
|
||||
cmd "focus parent";
|
||||
cmd "focus parent";
|
||||
|
||||
# try to kill the workspace
|
||||
cmd "kill";
|
||||
sync_with_i3;
|
||||
|
||||
# the workspace should now be empty
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$focus, 0, 'workspace is empty');
|
||||
|
||||
##############################################################
|
||||
# and now for something completely different:
|
||||
# check if the pointer position is relevant when restoring focus
|
||||
|
|
|
@ -49,29 +49,9 @@ wait_for_unmap $window;
|
|||
cmp_ok(@content, '==', 0, 'no more nodes');
|
||||
diag('content = '. Dumper(\@content));
|
||||
|
||||
|
||||
# TODO: move this to X11::XCB::Window
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
$window = open_window(
|
||||
name => 'Borderless window',
|
||||
before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
|
||||
wm_class => 'borderless',
|
||||
);
|
||||
|
||||
@content = @{get_ws_content($tmp)};
|
||||
|
@ -190,7 +170,7 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window(
|
||||
name => 'usethis',
|
||||
before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
|
||||
wm_class => 'borderless',
|
||||
);
|
||||
|
||||
@content = @{get_ws_content($tmp)};
|
||||
|
@ -208,8 +188,7 @@ sync_with_i3;
|
|||
cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
|
||||
|
||||
$window->_create;
|
||||
|
||||
set_wm_class($window->id, 'borderless', 'borderless');
|
||||
$window->wm_class('borderless');
|
||||
$window->name('notthis');
|
||||
$window->map;
|
||||
wait_for_map $window;
|
||||
|
@ -238,7 +217,8 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window(
|
||||
name => 'usethis',
|
||||
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
|
||||
wm_class => 'bar',
|
||||
instance => 'foo',
|
||||
);
|
||||
|
||||
@content = @{get_ws_content($tmp)};
|
||||
|
@ -264,7 +244,8 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window(
|
||||
name => 'usethis',
|
||||
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
|
||||
wm_class => 'bar',
|
||||
instance => 'foo',
|
||||
);
|
||||
|
||||
@content = @{get_ws_content($tmp)};
|
||||
|
@ -292,7 +273,8 @@ $tmp = fresh_workspace;
|
|||
|
||||
$window = open_window(
|
||||
name => 'usethis',
|
||||
before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
|
||||
wm_class => 'bar',
|
||||
instance => 'foo',
|
||||
);
|
||||
|
||||
@content = @{get_ws_content($tmp)};
|
||||
|
|
|
@ -17,37 +17,16 @@
|
|||
# Tests if assignments work
|
||||
#
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
$args{name} //= 'special window';
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
|
|
|
@ -18,37 +18,16 @@
|
|||
# assigned to an invisible workspace
|
||||
#
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
$args{name} //= 'special window';
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
|
|
|
@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv;
|
|||
is($bar_config->{status_command}, 'i3status --foo', 'status_command correct');
|
||||
ok(!$bar_config->{verbose}, 'verbose off by default');
|
||||
ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default');
|
||||
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default');
|
||||
is($bar_config->{mode}, 'dock', 'dock mode by default');
|
||||
is($bar_config->{position}, 'bottom', 'position bottom by default');
|
||||
|
||||
|
@ -85,7 +86,8 @@ $config = <<EOT;
|
|||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
# Start a default instance of i3bar which provides workspace buttons.
|
||||
# Start a default instance of i3bar which does not provide
|
||||
# workspace buttons.
|
||||
# Additionally, i3status will provide a statusline.
|
||||
status_command i3status --bar
|
||||
|
||||
|
@ -98,6 +100,7 @@ bar {
|
|||
mode dock
|
||||
font Terminus
|
||||
workspace_buttons no
|
||||
binding_mode_indicator no
|
||||
verbose yes
|
||||
socket_path /tmp/foobar
|
||||
|
||||
|
@ -125,6 +128,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
|
|||
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
|
||||
ok($bar_config->{verbose}, 'verbose on');
|
||||
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
|
||||
ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled');
|
||||
is($bar_config->{mode}, 'dock', 'dock mode');
|
||||
is($bar_config->{position}, 'top', 'position top');
|
||||
is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
|
||||
|
@ -230,7 +234,8 @@ $config = <<EOT;
|
|||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
# Start a default instance of i3bar which provides workspace buttons.
|
||||
# Start a default instance of i3bar which does not provide
|
||||
# workspace buttons.
|
||||
# Additionally, i3status will provide a statusline.
|
||||
status_command i3status --bar
|
||||
|
||||
|
@ -243,6 +248,7 @@ bar {
|
|||
mode dock
|
||||
font Terminus
|
||||
workspace_buttons no
|
||||
binding_mode_indicator yes
|
||||
verbose yes
|
||||
socket_path /tmp/foobar
|
||||
|
||||
|
@ -271,6 +277,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
|
|||
is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
|
||||
ok($bar_config->{verbose}, 'verbose on');
|
||||
ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
|
||||
ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled');
|
||||
is($bar_config->{mode}, 'dock', 'dock mode');
|
||||
is($bar_config->{position}, 'top', 'position top');
|
||||
is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok');
|
||||
|
|
|
@ -446,4 +446,29 @@ is(get_focused($ws), $scratch, 'scratchpad is focused');
|
|||
|
||||
# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
|
||||
|
||||
################################################################################
|
||||
# 14: Verify that 'move scratchpad' sends floating containers to scratchpad but
|
||||
# does not resize/resposition the container on the next 'scratchpad show', i.e.,
|
||||
# i3 sets the scratchpad flag to SCRATCHPAD_CHANGED
|
||||
################################################################################
|
||||
|
||||
clear_scratchpad;
|
||||
$tmp = fresh_workspace;
|
||||
open_window;
|
||||
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$nodes, 1, 'precisely one window on current ws');
|
||||
is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
|
||||
|
||||
cmd 'floating toggle';
|
||||
cmd 'move scratchpad';
|
||||
|
||||
$__i3_scratch = get_ws('__i3_scratch');
|
||||
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
|
||||
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
|
||||
($nodes, $focus) = get_ws_content($tmp);
|
||||
is(scalar @$nodes, 0, 'no window on current ws anymore');
|
||||
|
||||
is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -171,4 +171,19 @@ is(parser_calls('workspace "foo \"bar"'),
|
|||
'cmd_workspace_name(foo "bar)',
|
||||
'Command with escaped double quotes ok');
|
||||
|
||||
################################################################################
|
||||
# 4: Verify that resize commands with a "px or ppt"-construction are parsed
|
||||
# correctly
|
||||
################################################################################
|
||||
|
||||
is(parser_calls("resize shrink width 10 px or"),
|
||||
"ERROR: Expected one of these tokens: <word>\n" .
|
||||
"ERROR: Your command: resize shrink width 10 px or\n" .
|
||||
"ERROR: ",
|
||||
"error for resize command with incomplete 'or'-construction ok");
|
||||
|
||||
is(parser_calls("resize grow left 10 px or 20 ppt"),
|
||||
"cmd_resize(grow, left, 10, 20)",
|
||||
"resize command with 'or'-construction ok");
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -18,44 +18,13 @@
|
|||
# window is shown on another workspace.
|
||||
#
|
||||
use i3test;
|
||||
use List::Util qw(first);
|
||||
use X11::XCB qw(:all);
|
||||
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
|
||||
return open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
);
|
||||
}
|
||||
|
||||
my $win = open_window;
|
||||
|
||||
my $scratch = open_special;
|
||||
my $scratch = open_window(wm_class => 'special');
|
||||
cmd '[class="special"] move scratchpad';
|
||||
|
||||
is_num_children($tmp, 1, 'one window on current ws');
|
||||
|
|
|
@ -85,6 +85,7 @@ my $w2 = open_window;
|
|||
is($x->input_focus, $w2->id, 'window 2 focused');
|
||||
|
||||
cmd "workspace $tmp2";
|
||||
$w->delete_hint('urgency');
|
||||
$w->add_hint('urgency');
|
||||
sync_with_i3;
|
||||
|
||||
|
|
|
@ -627,7 +627,7 @@ EOT
|
|||
|
||||
$expected = <<'EOT';
|
||||
cfg_bar_output(LVDS-1)
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: bar {
|
||||
ERROR: CONFIG: Line 2: output LVDS-1
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
# Ticket: #909
|
||||
# Bug still in: 4.4-69-g6856b23
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(:all);
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
|
@ -31,28 +30,10 @@ EOT
|
|||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
before_map => sub { set_wm_class($_->id, '__i3-test-window', '__i3-test-window') },
|
||||
wm_class => '__i3-test-window',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
|
|
|
@ -20,41 +20,11 @@
|
|||
# Ticket: #913
|
||||
# Bug still in: 4.4-97-gf767ac3
|
||||
use i3test;
|
||||
use X11::XCB qw(:all);
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
|
||||
return open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
);
|
||||
}
|
||||
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
# Open a new window which we can identify later on based on its WM_CLASS.
|
||||
my $scratch = open_special;
|
||||
my $scratch = open_window(wm_class => 'special');
|
||||
|
||||
my $tmp2 = fresh_workspace;
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
# Ticket: #1027
|
||||
# Bug still in: 4.5.1-90-g6582da9
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
my $config = <<'EOT';
|
||||
# i3 config file (v4)
|
||||
|
@ -31,28 +30,9 @@ EOT
|
|||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
# TODO: move this to X11::XCB::Window
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
my $window = open_window(
|
||||
name => 'Borderless window',
|
||||
before_map => sub { set_wm_class($_->id, 'special', 'special') },
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
|
|
|
@ -21,37 +21,16 @@
|
|||
# Ticket: #1086
|
||||
# Bug still in: 4.6-62-g7098ef6
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(:all);
|
||||
|
||||
# TODO: move to X11::XCB
|
||||
sub set_wm_class {
|
||||
my ($id, $class, $instance) = @_;
|
||||
|
||||
# Add a _NET_WM_STRUT_PARTIAL hint
|
||||
my $atomname = $x->atom(name => 'WM_CLASS');
|
||||
my $atomtype = $x->atom(name => 'STRING');
|
||||
|
||||
$x->change_property(
|
||||
PROP_MODE_REPLACE,
|
||||
$id,
|
||||
$atomname->id,
|
||||
$atomtype->id,
|
||||
8,
|
||||
length($class) + length($instance) + 2,
|
||||
"$instance\x00$class\x00"
|
||||
);
|
||||
}
|
||||
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
my $wm_class = delete($args{wm_class}) || 'special';
|
||||
$args{name} //= 'special window';
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
%args,
|
||||
before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->add_hint('urgency');
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • http://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Tests if the urgency hint will be set appropriately when opening a window
|
||||
# assigned to a workspace.
|
||||
#
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# Based on the eponymous function in t/166-assign.t
|
||||
sub open_special {
|
||||
my %args = @_;
|
||||
$args{name} //= 'special window';
|
||||
|
||||
# We use dont_map because i3 will not map the window on the current
|
||||
# workspace. Thus, open_window would time out in wait_for_map (2 seconds).
|
||||
my $window = open_window(
|
||||
%args,
|
||||
wm_class => 'special',
|
||||
dont_map => 1,
|
||||
);
|
||||
$window->map;
|
||||
return $window;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# start a window assigned to a non-visible workspace and see that the urgency
|
||||
# hint is set.
|
||||
#####################################################################
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
assign [class="special"] → targetws
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
cmd 'workspace ordinaryws';
|
||||
my $window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(get_ws('targetws')->{urgent}, 'target workspace is urgent');
|
||||
|
||||
$window->destroy;
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
|
||||
#####################################################################
|
||||
# start a window assigned to a visible workspace and see that the urgency hint
|
||||
# is not set.
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
assign [class="special"] → targetws
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
cmd 'workspace targetws';
|
||||
$window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(!get_ws('targetws')->{urgent}, 'visible workspace is not urgent');
|
||||
|
||||
$window->destroy;
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# start a window assigned to a visible workspace on a different output and see
|
||||
# that the urgency hint is not set.
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0
|
||||
workspace targetws output fake-0
|
||||
workspace ordinaryws output fake-1
|
||||
|
||||
assign [class="special"] → targetws
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
cmd 'workspace ordinaryws';
|
||||
$window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(!get_ws('targetws')->{urgent}, 'target workspace is not urgent');
|
||||
|
||||
$window->destroy;
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
|
@ -71,6 +71,23 @@ is(focused_output, 'fake-1', 'focus on second output');
|
|||
cmd 'focus output fake-0';
|
||||
is(focused_output, 'fake-0', 'focus on first output');
|
||||
|
||||
################################################################################
|
||||
# use 'focus output' and verify that i3 does not crash when the currently
|
||||
# focused window is floating and is only partially mapped on an output screen
|
||||
################################################################################
|
||||
|
||||
is(focused_output, 'fake-0', 'focus on first output');
|
||||
|
||||
my $floating_win = open_window;
|
||||
cmd 'floating toggle';
|
||||
cmd 'move to absolute position -10 -10';
|
||||
|
||||
cmd 'focus output right';
|
||||
is(focused_output, 'fake-1', 'focus on second output');
|
||||
|
||||
cmd 'focus output fake-0';
|
||||
is(focused_output, 'fake-0', 'focus on first output');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# Ensure the pointer is at (0, 0) so that we really start on the first
|
||||
# (the left) workspace.
|
||||
$x->root->warp_pointer(0, 0);
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
@ -35,7 +39,7 @@ $i3->connect()->recv;
|
|||
# Workspaces requests and events
|
||||
################################
|
||||
|
||||
my $focused = get_ws(focused_ws());
|
||||
my $old_ws = get_ws(focused_ws);
|
||||
|
||||
# Events
|
||||
|
||||
|
@ -46,17 +50,11 @@ $i3->subscribe({
|
|||
workspace => sub {
|
||||
my ($event) = @_;
|
||||
if ($event->{change} eq 'focus') {
|
||||
# Check that we have the old and new workspace
|
||||
$focus->send(
|
||||
$event->{current}->{name} == '2' &&
|
||||
$event->{old}->{name} == $focused->{name}
|
||||
);
|
||||
$focus->send($event);
|
||||
}
|
||||
}
|
||||
})->recv;
|
||||
|
||||
cmd 'focus output right';
|
||||
|
||||
my $t;
|
||||
$t = AnyEvent->timer(
|
||||
after => 0.5,
|
||||
|
@ -65,7 +63,15 @@ $t = AnyEvent->timer(
|
|||
}
|
||||
);
|
||||
|
||||
ok($focus->recv, 'Workspace "focus" event received');
|
||||
cmd 'focus output right';
|
||||
|
||||
my $event = $focus->recv;
|
||||
|
||||
my $current_ws = get_ws(focused_ws);
|
||||
|
||||
ok($event, 'Workspace "focus" event received');
|
||||
is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
|
||||
is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • http://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Tests that new workspace names are taken from the config,
|
||||
# then from the first free number starting with 1.
|
||||
#
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0
|
||||
|
||||
bindsym 1 workspace 1: eggs
|
||||
EOT
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $ws = $i3->get_workspaces->recv;
|
||||
|
||||
is($ws->[0]->{name}, '1: eggs', 'new workspace uses config name');
|
||||
is($ws->[1]->{name}, '2', 'naming continues with next free number');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
|
@ -0,0 +1,106 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • http://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Tests if a simple 'move <direction>' command will move containers across outputs.
|
||||
#
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# Ensure the pointer is at (0, 0) so that we really start on the first
|
||||
# (the left) workspace.
|
||||
$x->root->warp_pointer(0, 0);
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
|
||||
|
||||
workspace left-top output fake-0
|
||||
workspace right-top output fake-1
|
||||
workspace right-bottom output fake-2
|
||||
workspace left-bottom output fake-3
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
#####################################################################
|
||||
# Try to move a single window across outputs in each direction
|
||||
#####################################################################
|
||||
|
||||
cmd('workspace left-top');
|
||||
my $alone_window = open_window;
|
||||
|
||||
cmd('move right');
|
||||
is(scalar @{get_ws_content('right-top')}, 1, 'moved individual window to right-top workspace');
|
||||
|
||||
cmd('move down');
|
||||
is(scalar @{get_ws_content('right-bottom')}, 1, 'moved individual window to right-bottom workspace');
|
||||
|
||||
cmd('move left');
|
||||
is(scalar @{get_ws_content('left-bottom')}, 1, 'moved individual window to left-bottom workspace');
|
||||
|
||||
cmd('move up');
|
||||
is(scalar @{get_ws_content('left-top')}, 1, 'moved individual window to left-top workspace');
|
||||
|
||||
$alone_window->unmap;
|
||||
wait_for_unmap;
|
||||
|
||||
#####################################################################
|
||||
# Try to move a window on a workspace with two windows across outputs in each
|
||||
# direction
|
||||
#####################################################################
|
||||
|
||||
# from left-top to right-top
|
||||
cmd('workspace left-top');
|
||||
cmd('split h');
|
||||
my $first_window = open_window;
|
||||
my $social_window = open_window( name => 'CORRECT_WINDOW' );
|
||||
cmd('move right');
|
||||
is(scalar @{get_ws_content('right-top')}, 1, 'moved some window to right-top workspace');
|
||||
my $compare_window = shift @{get_ws_content('right-top')};
|
||||
is($compare_window->{window}, $social_window->id, 'moved correct window to right-top workspace');
|
||||
# unmap the first window so we don't confuse it when we move back here
|
||||
$first_window->unmap;
|
||||
wait_for_unmap;
|
||||
|
||||
# from right-top to right-bottom
|
||||
cmd('split v');
|
||||
open_window;
|
||||
# this window opened above - we need to move down twice
|
||||
cmd('focus up; move down; move down');
|
||||
is(scalar @{get_ws_content('right-bottom')}, 1, 'moved some window to right-bottom workspace');
|
||||
$compare_window = shift @{get_ws_content('right-bottom')};
|
||||
is($compare_window->{name}, $social_window->name, 'moved correct window to right-bottom workspace');
|
||||
|
||||
# from right-bottom to left-bottom
|
||||
cmd('split h');
|
||||
open_window;
|
||||
cmd('focus left; move left');
|
||||
is(scalar @{get_ws_content('left-bottom')}, 1, 'moved some window to left-bottom workspace');
|
||||
$compare_window = shift @{get_ws_content('left-bottom')};
|
||||
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
|
||||
|
||||
# from left-bottom to left-top
|
||||
cmd('split v');
|
||||
open_window;
|
||||
cmd('focus up; move up');
|
||||
is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom workspace');
|
||||
$compare_window = shift @{get_ws_content('left-top')};
|
||||
is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
Loading…
Reference in New Issue