Merge branch 'next'
This commit is contained in:
commit
a1691a08ba
10
.gitignore
vendored
10
.gitignore
vendored
@ -5,14 +5,6 @@ include/all.h.pch
|
||||
*.swp
|
||||
*.gcda
|
||||
*.gcno
|
||||
testcases/testsuite-*
|
||||
testcases/latest
|
||||
testcases/Makefile
|
||||
testcases/Makefile.old
|
||||
testcases/.last_run_timings.json
|
||||
testcases/_Inline
|
||||
testcases/inc
|
||||
testcases/META.yml
|
||||
test.commands_parser
|
||||
test.config_parser
|
||||
*.output
|
||||
@ -32,3 +24,5 @@ libi3.a
|
||||
docs/*.pdf
|
||||
docs/*.html
|
||||
!/docs/refcard.html
|
||||
i3-command-parser.stamp
|
||||
i3-config-parser.stamp
|
||||
|
4
DEPENDS
4
DEPENDS
@ -10,9 +10,7 @@
|
||||
│ 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/ │
|
||||
│ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │
|
||||
│ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │
|
||||
│ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │
|
||||
│ 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/ │
|
||||
|
103
RELEASE-NOTES-4.5
Normal file
103
RELEASE-NOTES-4.5
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.5 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is the i3 v4.5. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
Most of the changes are cleanups and bugfixes. Due to cleanups, i3 no longer
|
||||
depends on flex/bison at all. Furthermore, libev ≥ 4 is now a hard dependency
|
||||
(libev < 4 is not supported anymore).
|
||||
|
||||
One important change to note is that moving windows to a different output will
|
||||
no longer move focus to that output. If you want to have the old behavior,
|
||||
modify the keybindings for moving in your configfile like this:
|
||||
|
||||
bindsym $mod+Shift+1 move workspace 1; workspace 1
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in v4.5 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• docs/hacking-howto: refer people to cr.i3wm.org
|
||||
• docs/ipc: Adds Go IPC lib to the docs.
|
||||
• docs/userguide: remove obsolete sentence about client.background
|
||||
• docs/userguide: be explicit about assignment processing order
|
||||
• docs/userguide: be more clear about the resize command arguments
|
||||
• docs/userguide: fix typo: s/11x/11px/
|
||||
• i3-dmenu-desktop: don’t add “geany” if “Geany” is already present
|
||||
• i3-dmenu-desktop: strip newlines from dmenu ≥ 4.4
|
||||
• i3-dmenu-desktop: skip files with broken utf8 but warn about it
|
||||
• i3-dmenu-desktop: skip broken files (no/empty Exec=) but warn about them
|
||||
• i3-dmenu-desktop: List filenames of .desktop files
|
||||
• i3-dmenu-desktop: remove %i from commandline
|
||||
• i3-nagbar: Work around terminals not supporting -e with quoted arguments
|
||||
• i3-nagbar: use the same font as configured for i3
|
||||
• i3bar: set _NET_SYSTEM_TRAY_COLORS for symbolic icons (gtk3+)
|
||||
• i3bar: don’t use X11 borders to avoid overlapping in hide mode
|
||||
• i3bar: separator color via config; separator width and on/off via ipc
|
||||
• i3bar: Allow min_width of a block in i3bar to be a string
|
||||
• i3-msg: parse command replies and display errors nicely if there were
|
||||
errors
|
||||
• Draw 1px tab separators left/right instead of 2px on the right only
|
||||
• Render tree before destroying X11 containers upon unmap
|
||||
• scratchpad show: move visible scratchpad window from another workspace to
|
||||
focused workspace instead of doing nothing
|
||||
• ignore MotionNotify events generated while warping the pointer
|
||||
• Allow X11 servers which do not support the XKB extension.
|
||||
• remove the urgency indicator when a window is closed
|
||||
• wrap when moving containers to outputs with direction
|
||||
• scratchpad_show: focus unfocused scratchpad window
|
||||
• Split workspace instead of changing orientation
|
||||
• scratchpad: always auto center on 'scratchpad show' if window hasn't been
|
||||
repositioned by the user
|
||||
• Add a new IPC event for changes on windows.
|
||||
• config: accept “smart” as popup_during_fullscreen parameter
|
||||
• Add support for _NET_WM_STATE_DEMANDS_ATTENTION.
|
||||
• Obey WM_SIZE_HINTS's resize increments in floating mode
|
||||
• Do not move focus if a container is moved across outputs
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• Ignore ConfigureRequests for scratchpad windows
|
||||
• Correctly parse `move ... workspace *_on_output`
|
||||
• i3bar: Set separator color properly when drawing
|
||||
• Properly parse commands like “move workspace torrent”
|
||||
• Handle nested transient popups properly
|
||||
• Fix decoration rect size for windows without border
|
||||
• parse outputs as "word", not "string", to ignore trailing whitespace
|
||||
• fix crash when disabling output without any windows
|
||||
• scratchpad: fix crash when moving last window of an invisible workspace
|
||||
• fix coordinates of scratchpad windows on output changes
|
||||
• call scratchpad_show() when focusing scratchpad windows via criteria
|
||||
• fix continuous resize bug in floating mode, e.g. with xbmc
|
||||
• fix “overlapping” --release key bindings
|
||||
• fix IPC messages writes with low buffer sizes
|
||||
• unregister as window manager before restarting (fixes a race condition)
|
||||
• Fix bind[code|sym] --release
|
||||
• remove superfluous #include <xcb/xcb_atom.h>
|
||||
• Makefile: Repect AR environment variable
|
||||
• i3-input: restore input focus on exit()
|
||||
• Also draw right tab border for split containers
|
||||
• Fix scrolling on a tabbed titlebar which contains split cons
|
||||
• Correctly close floating windows
|
||||
• handle MapRequests sent between i3 registering as a wm and handling events
|
||||
• i3bar: fake DestroyNotify and send MANAGER ClientMessages to fix tray restarts
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Adrien Schildknecht, alex, András Mohari, Artem Shinkarov, badboy, bafain,
|
||||
cradle, dcoppa, Donald, dRbiG, eeemsi, else, emias, f8l, Francesco Mazzoli,
|
||||
jasper, joepd, Kacper Kowalik, Kai, knopwob, Marcos, Marius Muja, Mats,
|
||||
MeanEYE, Merovius, oblique, paolo, phlux, Piotr S. Staszewski, pnutzh4x0r,
|
||||
rasi, saurabhgeek92, Sebastian Rachuj, Sebastian Ullrich, slowpoke, Steven
|
||||
Allen, supplantr, Tai-Lin Chu, Tucos, Vivien Didelot, xeen
|
||||
|
||||
-- Michael Stapelberg, 2013-03-12
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
i3-wm (4.4.1-0) unstable; urgency=low
|
||||
|
||||
* NOT YET RELEASED
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 12 Dec 2012 00:23:32 +0100
|
||||
|
||||
i3-wm (4.4-1) experimental; urgency=low
|
||||
|
||||
* New upstream release
|
||||
|
4
debian/control
vendored
4
debian/control
vendored
@ -14,9 +14,7 @@ Build-Depends: debhelper (>= 7.0.50~),
|
||||
xmlto,
|
||||
docbook-xml,
|
||||
pkg-config,
|
||||
libev-dev,
|
||||
flex,
|
||||
bison,
|
||||
libev-dev (>= 1:4.04),
|
||||
libyajl-dev,
|
||||
libpcre3-dev,
|
||||
libstartup-notification0-dev (>= 0.10),
|
||||
|
@ -1,7 +1,7 @@
|
||||
Hacking i3: How To
|
||||
==================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
July 2011
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
February 2013
|
||||
|
||||
This document is intended to be the first thing you read before looking and/or
|
||||
touching i3’s source code. It should contain all important information to help
|
||||
@ -28,7 +28,8 @@ In the case of i3, the tasks (and order of them) are the following:
|
||||
the first client of X) and manage them (reparent them, create window
|
||||
decorations, etc.)
|
||||
. When new windows are created, manage them
|
||||
. Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
|
||||
. Handle the client’s `_WM_STATE` property, but only `_WM_STATE_FULLSCREEN` and
|
||||
`_NET_WM_STATE_DEMANDS_ATTENTION`
|
||||
. Handle the client’s `WM_NAME` property
|
||||
. Handle the client’s size hints to display them proportionally
|
||||
. Handle the client’s urgency hint
|
||||
@ -947,31 +948,20 @@ For a short introduction into using git, see
|
||||
http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
|
||||
http://git-scm.com/documentation
|
||||
|
||||
When you want to send a patch because you fixed a bug or implemented a cool
|
||||
feature (please talk to us before working on features to see whether they are
|
||||
maybe already implemented, not possible for some some reason, or don’t fit
|
||||
into the concept), please use git to create a patchfile.
|
||||
Please talk to us before working on new features to see whether they will be
|
||||
accepted. There are a few things which we don’t want to see in i3, e.g. a
|
||||
command which will focus windows in an alt+tab like way.
|
||||
|
||||
First of all, update your working copy to the latest version of the master
|
||||
branch:
|
||||
When working on bugfixes, please make sure you mention that you are working on
|
||||
it in the corresponding bugreport at http://bugs.i3wm.org/. In case there is no
|
||||
bugreport yet, please create one.
|
||||
|
||||
--------
|
||||
git pull
|
||||
--------
|
||||
After you are done, please submit your work for review at http://cr.i3wm.org/
|
||||
|
||||
Afterwards, make the necessary changes for your bugfix/feature. Then, review
|
||||
the changes using +git diff+ (you might want to enable colors in the diff using
|
||||
+git config diff.color auto+). When you are definitely done, use +git commit
|
||||
-a+ to commit all changes you’ve made.
|
||||
|
||||
Then, use the following command to generate a patchfile which we can directly
|
||||
apply to the branch, preserving your commit message and name:
|
||||
|
||||
-----------------------
|
||||
git format-patch origin
|
||||
-----------------------
|
||||
|
||||
Just send us the generated file via email.
|
||||
Do not send emails to the mailing list or any author directly, and don’t submit
|
||||
them in the bugtracker, since all reviews should be done in public at
|
||||
http://cr.i3wm.org/. In order to make your review go as fast as possible, you
|
||||
could have a look at previous reviews and see what the common mistakes are.
|
||||
|
||||
== Thought experiments
|
||||
|
||||
|
@ -30,7 +30,7 @@ $parser->html_header_before_title(
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
<meta charset="utf-8">
|
||||
<meta name="generator" content="Pod::Simple::HTML">
|
||||
<meta name="description" content="i3 Perl documentation (testsuite)">
|
||||
<meta name="description" content="i3 Perl documentation">
|
||||
<link rel="stylesheet" href="http://i3wm.org/css/style.css" type="text/css" />
|
||||
<style type="text/css">
|
||||
.pod pre {
|
||||
@ -81,7 +81,7 @@ $parser->html_header_after_title(
|
||||
</ul>
|
||||
<br style="clear: both">
|
||||
<div id="content" class="pod">
|
||||
<h1>i3 Perl documentation (testsuite)</h1>
|
||||
<h1>i3 Perl documentation</h1>
|
||||
|
||||
EOF
|
||||
);
|
||||
|
@ -140,6 +140,10 @@ min_width::
|
||||
will be padded to the left and/or the right side, according to the +align+
|
||||
key. This is useful when you want to prevent the whole status line to shift
|
||||
when value take more or less space between each iteration.
|
||||
The value can also be a string. In this case, the width of the text given
|
||||
by +min_width+ determines the minimum width of the block. This is useful
|
||||
when you want to set a sensible minimum width regardless of which font you
|
||||
are using, and at what particular size.
|
||||
align::
|
||||
Align text on the +center+ (default), +right+ or +left+ of the block, when
|
||||
the minimum width of the latter, specified by the +min_width+ key, is not
|
||||
@ -154,6 +158,16 @@ urgent::
|
||||
A boolean which specifies whether the current value is urgent. Examples
|
||||
are battery charge values below 1 percent or no more available disk
|
||||
space (for non-root users). The presentation of urgency is up to i3bar.
|
||||
separator::
|
||||
A boolean which specifies whether a separator line should be drawn
|
||||
after this block. The default is true, meaning the separator line will
|
||||
be drawn. Note that if you disable the separator line, there will still
|
||||
be a gap after the block, unless you also use +separator_block_width+.
|
||||
separator_block_width::
|
||||
The amount of pixels to leave blank after the block. In the middle of
|
||||
this gap, a separator line will be drawn unless +separator+ is
|
||||
disabled. Normally, you want to set this to an odd value (the default
|
||||
is 9 pixels), since the separator line is drawn in the middle.
|
||||
|
||||
If you want to put in your own entries into a block, prefix the key with an
|
||||
underscore (_). i3bar will ignore all keys it doesn’t understand, and prefixing
|
||||
@ -168,6 +182,17 @@ of the i3bar protocol.
|
||||
}
|
||||
------------------------------------------
|
||||
|
||||
In the following example, the longest (widest) possible value of the block is
|
||||
used to set the minimum width:
|
||||
|
||||
------------------------------------------
|
||||
{
|
||||
"full_text": "CPU 4%",
|
||||
"min_width": "CPU 100%",
|
||||
"align": "left"
|
||||
}
|
||||
------------------------------------------
|
||||
|
||||
An example of a block which uses all possible entries follows:
|
||||
|
||||
*Example*:
|
||||
@ -180,6 +205,8 @@ An example of a block which uses all possible entries follows:
|
||||
"align": "right",
|
||||
"urgent": false,
|
||||
"name": "ethernet",
|
||||
"instance": "eth0"
|
||||
"instance": "eth0",
|
||||
"separator": true,
|
||||
"separator_block_width": 9
|
||||
}
|
||||
------------------------------------------
|
||||
|
31
docs/ipc
31
docs/ipc
@ -507,6 +507,8 @@ background::
|
||||
Background color of the bar.
|
||||
statusline::
|
||||
Text color to be used for the statusline.
|
||||
separator::
|
||||
Text color to be used for the separator.
|
||||
focused_workspace_text/focused_workspace_bg::
|
||||
Text color/background color for a workspace button when the workspace
|
||||
has focus.
|
||||
@ -621,6 +623,9 @@ output (1)::
|
||||
outputs, CRTCs or output properties).
|
||||
mode (2)::
|
||||
Sent whenever i3 changes its binding mode.
|
||||
window (3)::
|
||||
Sent when a client's window is successfully reparented (that is when i3
|
||||
has finished fitting it into a container).
|
||||
|
||||
*Example:*
|
||||
--------------------------------------------------------------------
|
||||
@ -694,6 +699,30 @@ mode is simply named default.
|
||||
{ "change": "default" }
|
||||
---------------------------
|
||||
|
||||
=== window event
|
||||
|
||||
This event consists of a single serialized map containing a property
|
||||
+change (string)+ which currently can indicate only that a new window
|
||||
has been successfully reparented (the value will be "new").
|
||||
|
||||
Additionally a +container (object)+ field will be present, which consists
|
||||
of the window's parent container. Be aware that the container will hold
|
||||
the initial name of the newly reparented window (e.g. if you run urxvt
|
||||
with a shell that changes the title, you will still at this point get the
|
||||
window title as "urxvt").
|
||||
|
||||
*Example:*
|
||||
---------------------------
|
||||
{
|
||||
"change": "new",
|
||||
"container": {
|
||||
"id": 35569536,
|
||||
"type": 2,
|
||||
...
|
||||
}
|
||||
}
|
||||
---------------------------
|
||||
|
||||
== See also (existing libraries)
|
||||
|
||||
[[libraries]]
|
||||
@ -712,3 +741,5 @@ Perl::
|
||||
Python::
|
||||
* https://github.com/whitelynx/i3ipc
|
||||
* https://github.com/ziberna/i3-py (includes higher-level features)
|
||||
Go::
|
||||
* https://github.com/proxypoke/i3ipc
|
||||
|
@ -1,7 +1,7 @@
|
||||
i3 User’s Guide
|
||||
===============
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
August 2012
|
||||
February 2013
|
||||
|
||||
This document contains all the information you need to configure and use the i3
|
||||
window manager. If it does not, please check http://faq.i3wm.org/ first, then
|
||||
@ -324,7 +324,7 @@ font pango:[family list] [style options] [size]
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
font pango:DejaVu Sans Mono 10
|
||||
font pango:DejaVu Sans Mono, Terminus Bold Semi-Condensed 11
|
||||
font pango:Terminus 11x
|
||||
font pango:Terminus 11px
|
||||
--------------------------------------------------------------
|
||||
|
||||
[[keybindings]]
|
||||
@ -590,6 +590,10 @@ title change. As i3 will get the title as soon as the application maps the
|
||||
window (mapping means actually displaying it on the screen), you’d need to have
|
||||
to match on 'Firefox' in this case.
|
||||
|
||||
Assignments are processed by i3 in the order in which they appear in the config
|
||||
file. The first one which matches the window wins and later assignments are not
|
||||
considered.
|
||||
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
assign <criteria> [→] workspace
|
||||
@ -732,10 +736,7 @@ client.background color
|
||||
-----------------------
|
||||
|
||||
Only clients that do not cover the whole area of this window expose the color
|
||||
used to paint it. If you use a color other than black for your terminals, you
|
||||
most likely want to set the client background color to the same color as your
|
||||
terminal program's background color to avoid black gaps between the rendered
|
||||
area of the terminal and the i3 border.
|
||||
used to paint it.
|
||||
|
||||
Colors are in HTML hex format (#rrggbb), see the following example:
|
||||
|
||||
@ -1154,6 +1155,8 @@ background::
|
||||
Background color of the bar.
|
||||
statusline::
|
||||
Text color to be used for the statusline.
|
||||
separator::
|
||||
Text color to be used for the separator.
|
||||
focused_workspace::
|
||||
Border, background and text color for a workspace button when the workspace
|
||||
has focus.
|
||||
@ -1175,6 +1178,7 @@ urgent_workspace::
|
||||
colors {
|
||||
background <color>
|
||||
statusline <color>
|
||||
separator <color>
|
||||
|
||||
colorclass <border> <background> <text>
|
||||
}
|
||||
@ -1186,6 +1190,7 @@ bar {
|
||||
colors {
|
||||
background #000000
|
||||
statusline #ffffff
|
||||
separator #666666
|
||||
|
||||
focused_workspace #4c7899 #285577 #ffffff
|
||||
active_workspace #333333 #5f676a #ffffff
|
||||
|
@ -1,105 +0,0 @@
|
||||
%option nounput
|
||||
%option noinput
|
||||
%option noyy_top_state
|
||||
%option stack
|
||||
|
||||
%{
|
||||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "cfgparse.tab.h"
|
||||
|
||||
int yycolumn = 1;
|
||||
|
||||
struct context {
|
||||
int line_number;
|
||||
char *line_copy;
|
||||
|
||||
char *compact_error;
|
||||
|
||||
/* These are the same as in YYLTYPE */
|
||||
int first_column;
|
||||
int last_column;
|
||||
};
|
||||
|
||||
|
||||
#define YY_DECL int yylex (struct context *context)
|
||||
|
||||
#define YY_USER_ACTION { \
|
||||
context->first_column = yycolumn; \
|
||||
context->last_column = yycolumn+yyleng-1; \
|
||||
yycolumn += yyleng; \
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
EOL (\r?\n)
|
||||
|
||||
%s BINDCODE_COND
|
||||
%s BIND_AWS_COND
|
||||
%s BIND_A2WS_COND
|
||||
%x BUFFER_LINE
|
||||
|
||||
%%
|
||||
|
||||
{
|
||||
/* This is called when a new line is lexed. We only want the
|
||||
* first line to match to go into state BUFFER_LINE */
|
||||
if (context->line_number == 0) {
|
||||
context->line_number = 1;
|
||||
BEGIN(INITIAL);
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
|
||||
/* save whole line */
|
||||
context->line_copy = strdup(yytext);
|
||||
|
||||
yyless(0);
|
||||
yy_pop_state();
|
||||
yy_set_bol(true);
|
||||
yycolumn = 1;
|
||||
}
|
||||
|
||||
|
||||
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
|
||||
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
|
||||
bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
|
||||
bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; }
|
||||
Mod1 { yylval.number = (1 << 3); return MODIFIER; }
|
||||
Mod2 { yylval.number = (1 << 4); return MODIFIER; }
|
||||
Mod3 { yylval.number = (1 << 5); return MODIFIER; }
|
||||
Mod4 { yylval.number = (1 << 6); return MODIFIER; }
|
||||
Mod5 { yylval.number = (1 << 7); return MODIFIER; }
|
||||
Mode_switch { yylval.number = (1 << 8); return MODIFIER; }
|
||||
$mod { yylval.number = (1 << 9); return TOKMODVAR; }
|
||||
control { return TOKCONTROL; }
|
||||
ctrl { return TOKCONTROL; }
|
||||
shift { return TOKSHIFT; }
|
||||
{EOL} {
|
||||
if (context->line_copy) {
|
||||
free(context->line_copy);
|
||||
context->line_copy = NULL;
|
||||
}
|
||||
context->line_number++;
|
||||
BEGIN(INITIAL);
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
<BINDCODE_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
|
||||
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
|
||||
[ \t]+ { return WHITESPACE; }
|
||||
. { return (int)yytext[0]; }
|
||||
|
||||
<<EOF>> {
|
||||
while (yy_start_stack_ptr > 0)
|
||||
yy_pop_state();
|
||||
yyterminate();
|
||||
}
|
||||
|
||||
%%
|
@ -1,208 +0,0 @@
|
||||
%{
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
#include "libi3.h"
|
||||
|
||||
extern Display *dpy;
|
||||
|
||||
struct context {
|
||||
int line_number;
|
||||
char *line_copy;
|
||||
|
||||
char *compact_error;
|
||||
|
||||
/* These are the same as in YYLTYPE */
|
||||
int first_column;
|
||||
int last_column;
|
||||
|
||||
char *result;
|
||||
};
|
||||
|
||||
typedef struct yy_buffer_state *YY_BUFFER_STATE;
|
||||
extern int yylex(struct context *context);
|
||||
extern int yyparse(void);
|
||||
extern FILE *yyin;
|
||||
YY_BUFFER_STATE yy_scan_string(const char *);
|
||||
|
||||
static struct context *context;
|
||||
static xcb_connection_t *conn;
|
||||
static xcb_key_symbols_t *keysyms;
|
||||
|
||||
/* We don’t need yydebug for now, as we got decent error messages using
|
||||
* yyerror(). Should you ever want to extend the parser, it might be handy
|
||||
* to just comment it in again, so it stays here. */
|
||||
//int yydebug = 1;
|
||||
|
||||
void yyerror(const char *error_message) {
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "CONFIG: %s\n", error_message);
|
||||
fprintf(stderr, "CONFIG: line %d:\n",
|
||||
context->line_number);
|
||||
fprintf(stderr, "CONFIG: %s\n", context->line_copy);
|
||||
fprintf(stderr, "CONFIG: ");
|
||||
for (int c = 1; c <= context->last_column; c++)
|
||||
if (c >= context->first_column)
|
||||
fprintf(stderr, "^");
|
||||
else fprintf(stderr, " ");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int yywrap() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *rewrite_binding(const char *bindingline) {
|
||||
char *result = NULL;
|
||||
|
||||
conn = xcb_connect(NULL, NULL);
|
||||
if (conn == NULL || xcb_connection_has_error(conn)) {
|
||||
fprintf(stderr, "Cannot open display\n");
|
||||
exit(1);
|
||||
}
|
||||
keysyms = xcb_key_symbols_alloc(conn);
|
||||
|
||||
context = calloc(sizeof(struct context), 1);
|
||||
|
||||
yy_scan_string(bindingline);
|
||||
|
||||
if (yyparse() != 0) {
|
||||
fprintf(stderr, "Could not parse configfile\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
result = context->result;
|
||||
|
||||
if (context->line_copy)
|
||||
free(context->line_copy);
|
||||
free(context);
|
||||
xcb_key_symbols_free(keysyms);
|
||||
xcb_disconnect(conn);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* XXX: does not work for combinations of modifiers yet */
|
||||
static char *modifier_to_string(int modifiers) {
|
||||
//printf("should convert %d to string\n", modifiers);
|
||||
if (modifiers == (1 << 3))
|
||||
return strdup("$mod+");
|
||||
else if (modifiers == ((1 << 3) | (1 << 0)))
|
||||
return strdup("$mod+Shift+");
|
||||
else if (modifiers == (1 << 9))
|
||||
return strdup("$mod+");
|
||||
else if (modifiers == ((1 << 9) | (1 << 0)))
|
||||
return strdup("$mod+Shift+");
|
||||
else if (modifiers == (1 << 0))
|
||||
return strdup("Shift+");
|
||||
else return strdup("");
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if sym is bound to any key except for 'except_keycode' on the
|
||||
* first four layers (normal, shift, mode_switch, mode_switch + shift).
|
||||
*
|
||||
*/
|
||||
static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
|
||||
xcb_keycode_t i,
|
||||
min_keycode = xcb_get_setup(conn)->min_keycode,
|
||||
max_keycode = xcb_get_setup(conn)->max_keycode;
|
||||
|
||||
for (i = min_keycode; i && i <= max_keycode; i++) {
|
||||
if (i == except_keycode)
|
||||
continue;
|
||||
for (int level = 0; level < 4; level++) {
|
||||
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%error-verbose
|
||||
%lex-param { struct context *context }
|
||||
|
||||
%union {
|
||||
int number;
|
||||
char *string;
|
||||
}
|
||||
|
||||
%token <number>NUMBER "<number>"
|
||||
%token <string>STR "<string>"
|
||||
%token TOKBINDCODE
|
||||
%token TOKMODVAR "$mod"
|
||||
%token MODIFIER "<modifier>"
|
||||
%token TOKCONTROL "control"
|
||||
%token TOKSHIFT "shift"
|
||||
%token WHITESPACE "<whitespace>"
|
||||
|
||||
%%
|
||||
|
||||
lines: /* empty */
|
||||
| lines WHITESPACE bindcode
|
||||
| lines error
|
||||
| lines bindcode
|
||||
;
|
||||
|
||||
bindcode:
|
||||
TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR
|
||||
{
|
||||
//printf("\tFound keycode binding mod%d with key %d and command %s\n", $<number>3, $4, $<string>6);
|
||||
int level = 0;
|
||||
if (($<number>3 & (1 << 0))) {
|
||||
/* When shift is included, we really need to use the second-level
|
||||
* symbol (upper-case). The lower-case symbol could be on a
|
||||
* different key than the upper-case one (unlikely for letters, but
|
||||
* more likely for special characters). */
|
||||
level = 1;
|
||||
|
||||
/* Try to use the keysym on the first level (lower-case). In case
|
||||
* this doesn’t make it ambiguous (think of a keyboard layout
|
||||
* having '1' on two different keys, but '!' only on keycode 10),
|
||||
* we’ll stick with the keysym of the first level.
|
||||
*
|
||||
* This reduces a lot of confusion for users who switch keyboard
|
||||
* layouts from qwerty to qwertz or other slight variations of
|
||||
* qwerty (yes, that happens quite often). */
|
||||
KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, 0);
|
||||
if (!keysym_used_on_other_key(sym, $4))
|
||||
level = 0;
|
||||
}
|
||||
KeySym sym = XkbKeycodeToKeysym(dpy, $4, 0, level);
|
||||
char *str = XKeysymToString(sym);
|
||||
char *modifiers = modifier_to_string($<number>3);
|
||||
sasprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $<string>6);
|
||||
free(modifiers);
|
||||
}
|
||||
;
|
||||
|
||||
binding_modifiers:
|
||||
/* NULL */ { $<number>$ = 0; }
|
||||
| binding_modifier
|
||||
| binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
|
||||
| binding_modifiers '+' { $<number>$ = $<number>1; }
|
||||
;
|
||||
|
||||
binding_modifier:
|
||||
MODIFIER { $<number>$ = $<number>1; }
|
||||
| TOKMODVAR { $<number>$ = $<number>1; }
|
||||
| TOKCONTROL { $<number>$ = (1 << 2); }
|
||||
| TOKSHIFT { $<number>$ = (1 << 0); }
|
||||
;
|
@ -2,27 +2,18 @@ ALL_TARGETS += i3-config-wizard/i3-config-wizard
|
||||
INSTALL_TARGETS += install-i3-config-wizard
|
||||
CLEAN_TARGETS += clean-i3-config-wizard
|
||||
|
||||
i3_config_wizard_SOURCES_GENERATED = i3-config-wizard/cfgparse.tab.c i3-config-wizard/cfgparse.yy.c
|
||||
i3_config_wizard_SOURCES := $(filter-out $(i3_config_wizard_SOURCES_GENERATED),$(wildcard i3-config-wizard/*.c))
|
||||
i3_config_wizard_SOURCES := $(wildcard i3-config-wizard/*.c)
|
||||
i3_config_wizard_HEADERS := $(wildcard i3-config-wizard/*.h)
|
||||
i3_config_wizard_CFLAGS = $(XCB_CFLAGS) $(XCB_KBD_CFLAGS) $(X11_CFLAGS) $(PANGO_CFLAGS)
|
||||
i3_config_wizard_LIBS = $(XCB_LIBS) $(XCB_KBD_LIBS) $(X11_LIBS) $(PANGO_LIBS)
|
||||
|
||||
i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES_GENERATED:.c=.o) $(i3_config_wizard_SOURCES:.c=.o)
|
||||
i3_config_wizard_OBJECTS := $(i3_config_wizard_SOURCES:.c=.o)
|
||||
|
||||
|
||||
i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS)
|
||||
i3-config-wizard/%.o: i3-config-wizard/%.c $(i3_config_wizard_HEADERS) i3-config-parser.stamp
|
||||
echo "[i3-config-wizard] CC $<"
|
||||
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_config_wizard_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
i3-config-wizard/cfgparse.yy.c: i3-config-wizard/cfgparse.l i3-config-wizard/cfgparse.tab.o $(i3_config_wizard_HEADERS)
|
||||
echo "[i3-config-wizard] LEX $<"
|
||||
$(FLEX) -i -o $@ $<
|
||||
|
||||
i3-config-wizard/cfgparse.tab.c: i3-config-wizard/cfgparse.y $(i3_config_wizard_HEADERS)
|
||||
echo "[i3-config-wizard] YACC $<"
|
||||
$(BISON) --debug --verbose -b $(basename $< .y) -d $<
|
||||
|
||||
i3-config-wizard/i3-config-wizard: libi3.a $(i3_config_wizard_OBJECTS)
|
||||
echo "[i3-config-wizard] Link i3-config-wizard"
|
||||
$(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_config_wizard_LIBS)
|
||||
@ -34,4 +25,4 @@ install-i3-config-wizard: i3-config-wizard/i3-config-wizard
|
||||
|
||||
clean-i3-config-wizard:
|
||||
echo "[i3-config-wizard] Clean"
|
||||
rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.{output,dot,tab.h,y.o}
|
||||
rm -f $(i3_config_wizard_OBJECTS) $(i3_config_wizard_SOURCES_GENERATED) i3-config-wizard/i3-config-wizard i3-config-wizard/cfgparse.*
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <glob.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
@ -44,6 +45,7 @@
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
/* We need SYSCONFDIR for the path to the keycode config template, so raise an
|
||||
* error if it’s not defined for whatever reason */
|
||||
@ -68,6 +70,7 @@ enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
|
||||
static char *config_path;
|
||||
static uint32_t xcb_numlock_mask;
|
||||
xcb_connection_t *conn;
|
||||
static xcb_key_symbols_t *keysyms;
|
||||
xcb_screen_t *root_screen;
|
||||
static xcb_get_modifier_mapping_reply_t *modmap_reply;
|
||||
static i3Font font;
|
||||
@ -80,9 +83,342 @@ static xcb_key_symbols_t *symbols;
|
||||
xcb_window_t root;
|
||||
Display *dpy;
|
||||
|
||||
char *rewrite_binding(const char *bindingline);
|
||||
static void finish();
|
||||
|
||||
#include "GENERATED_config_enums.h"
|
||||
|
||||
typedef struct token {
|
||||
char *name;
|
||||
char *identifier;
|
||||
/* This might be __CALL */
|
||||
cmdp_state next_state;
|
||||
union {
|
||||
uint16_t call_identifier;
|
||||
} extra;
|
||||
} cmdp_token;
|
||||
|
||||
typedef struct tokenptr {
|
||||
cmdp_token *array;
|
||||
int n;
|
||||
} cmdp_token_ptr;
|
||||
|
||||
|
||||
#include "GENERATED_config_tokens.h"
|
||||
|
||||
static cmdp_state state;
|
||||
/* A list which contains the states that lead to the current state, e.g.
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
static cmdp_state statelist[10] = { INITIAL };
|
||||
/* NB: statelist_idx points to where the next entry will be inserted */
|
||||
static int statelist_idx = 1;
|
||||
|
||||
struct stack_entry {
|
||||
/* Just a pointer, not dynamically allocated. */
|
||||
const char *identifier;
|
||||
enum {
|
||||
STACK_STR = 0,
|
||||
STACK_LONG = 1,
|
||||
} type;
|
||||
union {
|
||||
char *str;
|
||||
long num;
|
||||
} val;
|
||||
};
|
||||
|
||||
/* 10 entries should be enough for everybody. */
|
||||
static struct stack_entry stack[10];
|
||||
|
||||
/*
|
||||
* Pushes a string (identified by 'identifier') on the stack. We simply use a
|
||||
* single array, since the number of entries we have to store is very small.
|
||||
*
|
||||
*/
|
||||
static void push_string(const char *identifier, const char *str) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL &&
|
||||
strcmp(stack[c].identifier, identifier) != 0)
|
||||
continue;
|
||||
if (stack[c].identifier == NULL) {
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.str = sstrdup(str);
|
||||
stack[c].type = STACK_STR;
|
||||
} else {
|
||||
/* Append the value. */
|
||||
char *prev = stack[c].val.str;
|
||||
sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
|
||||
free(prev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* When we arrive here, the stack is full. This should not happen and
|
||||
* means there’s either a bug in this parser or the specification
|
||||
* contains a command with more than 10 identified tokens. */
|
||||
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void push_long(const char *identifier, long num) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL)
|
||||
continue;
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.num = num;
|
||||
stack[c].type = STACK_LONG;
|
||||
return;
|
||||
}
|
||||
|
||||
/* When we arrive here, the stack is full. This should not happen and
|
||||
* means there’s either a bug in this parser or the specification
|
||||
* contains a command with more than 10 identified tokens. */
|
||||
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
|
||||
"in the code, or a new command which contains more than "
|
||||
"10 identified tokens.\n");
|
||||
exit(1);
|
||||
|
||||
}
|
||||
|
||||
static const char *get_string(const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
return stack[c].val.str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void clear_stack(void) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
|
||||
free(stack[c].val.str);
|
||||
stack[c].identifier = NULL;
|
||||
stack[c].val.str = NULL;
|
||||
stack[c].val.num = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if sym is bound to any key except for 'except_keycode' on the
|
||||
* first four layers (normal, shift, mode_switch, mode_switch + shift).
|
||||
*
|
||||
*/
|
||||
static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
|
||||
xcb_keycode_t i,
|
||||
min_keycode = xcb_get_setup(conn)->min_keycode,
|
||||
max_keycode = xcb_get_setup(conn)->max_keycode;
|
||||
|
||||
for (i = min_keycode; i && i <= max_keycode; i++) {
|
||||
if (i == except_keycode)
|
||||
continue;
|
||||
for (int level = 0; level < 4; level++) {
|
||||
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static char *next_state(const cmdp_token *token) {
|
||||
cmdp_state _next_state = token->next_state;
|
||||
|
||||
if (token->next_state == __CALL) {
|
||||
const char *modifiers = get_string("modifiers");
|
||||
int keycode = atoi(get_string("key"));
|
||||
int level = 0;
|
||||
if (modifiers != NULL &&
|
||||
strstr(modifiers, "Shift") != NULL) {
|
||||
/* When shift is included, we really need to use the second-level
|
||||
* symbol (upper-case). The lower-case symbol could be on a
|
||||
* different key than the upper-case one (unlikely for letters, but
|
||||
* more likely for special characters). */
|
||||
level = 1;
|
||||
|
||||
/* Try to use the keysym on the first level (lower-case). In case
|
||||
* this doesn’t make it ambiguous (think of a keyboard layout
|
||||
* having '1' on two different keys, but '!' only on keycode 10),
|
||||
* we’ll stick with the keysym of the first level.
|
||||
*
|
||||
* This reduces a lot of confusion for users who switch keyboard
|
||||
* layouts from qwerty to qwertz or other slight variations of
|
||||
* qwerty (yes, that happens quite often). */
|
||||
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, 0);
|
||||
if (!keysym_used_on_other_key(sym, keycode))
|
||||
level = 0;
|
||||
}
|
||||
KeySym sym = XkbKeycodeToKeysym(dpy, keycode, 0, level);
|
||||
char *str = XKeysymToString(sym);
|
||||
const char *release = get_string("release");
|
||||
char *res;
|
||||
char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
|
||||
char *comma;
|
||||
while ((comma = strchr(modrep, ',')) != NULL) {
|
||||
*comma = '+';
|
||||
}
|
||||
sasprintf(&res, "bindsym %s%s%s %s%s\n", (modifiers == NULL ? "" : modrep), (modifiers == NULL ? "" : "+"), str, (release == NULL ? "" : release), get_string("command"));
|
||||
clear_stack();
|
||||
return res;
|
||||
}
|
||||
|
||||
state = _next_state;
|
||||
|
||||
/* See if we are jumping back to a state in which we were in previously
|
||||
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
|
||||
for (int i = 0; i < statelist_idx; i++) {
|
||||
if (statelist[i] != _next_state)
|
||||
continue;
|
||||
statelist_idx = i+1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Otherwise, the state is new and we add it to the list */
|
||||
statelist[statelist_idx++] = _next_state;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static char *rewrite_binding(const char *input) {
|
||||
state = INITIAL;
|
||||
statelist_idx = 1;
|
||||
|
||||
const char *walk = input;
|
||||
const size_t len = strlen(input);
|
||||
int c;
|
||||
const cmdp_token *token;
|
||||
char *result = NULL;
|
||||
|
||||
/* The "<=" operator is intentional: We also handle the terminating 0-byte
|
||||
* explicitly by looking for an 'end' token. */
|
||||
while ((walk - input) <= len) {
|
||||
/* Skip whitespace before every token, newlines are relevant since they
|
||||
* separate configuration directives. */
|
||||
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
|
||||
walk++;
|
||||
|
||||
//printf("remaining input: %s\n", walk);
|
||||
|
||||
cmdp_token_ptr *ptr = &(tokens[state]);
|
||||
for (c = 0; c < ptr->n; c++) {
|
||||
token = &(ptr->array[c]);
|
||||
|
||||
/* A literal. */
|
||||
if (token->name[0] == '\'') {
|
||||
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
|
||||
if (token->identifier != NULL)
|
||||
push_string(token->identifier, token->name + 1);
|
||||
walk += strlen(token->name) - 1;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "number") == 0) {
|
||||
/* Handle numbers. We only accept decimal numbers for now. */
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
long int num = strtol(walk, &end, 10);
|
||||
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
|
||||
(errno != 0 && num == 0))
|
||||
continue;
|
||||
|
||||
/* No valid numbers found */
|
||||
if (end == walk)
|
||||
continue;
|
||||
|
||||
if (token->identifier != NULL)
|
||||
push_long(token->identifier, num);
|
||||
|
||||
/* Set walk to the first non-number character */
|
||||
walk = end;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "string") == 0 ||
|
||||
strcmp(token->name, "word") == 0) {
|
||||
const char *beginning = walk;
|
||||
/* Handle quoted strings (or words). */
|
||||
if (*walk == '"') {
|
||||
beginning++;
|
||||
walk++;
|
||||
while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\'))
|
||||
walk++;
|
||||
} else {
|
||||
if (token->name[0] == 's') {
|
||||
while (*walk != '\0' && *walk != '\r' && *walk != '\n')
|
||||
walk++;
|
||||
} else {
|
||||
/* For a word, the delimiters are white space (' ' or
|
||||
* '\t'), closing square bracket (]), comma (,) and
|
||||
* semicolon (;). */
|
||||
while (*walk != ' ' && *walk != '\t' &&
|
||||
*walk != ']' && *walk != ',' &&
|
||||
*walk != ';' && *walk != '\r' &&
|
||||
*walk != '\n' && *walk != '\0')
|
||||
walk++;
|
||||
}
|
||||
}
|
||||
if (walk != beginning) {
|
||||
char *str = scalloc(walk-beginning + 1);
|
||||
/* We copy manually to handle escaping of characters. */
|
||||
int inpos, outpos;
|
||||
for (inpos = 0, outpos = 0;
|
||||
inpos < (walk-beginning);
|
||||
inpos++, outpos++) {
|
||||
/* We only handle escaped double quotes to not break
|
||||
* backwards compatibility with people using \w in
|
||||
* regular expressions etc. */
|
||||
if (beginning[inpos] == '\\' && beginning[inpos+1] == '"')
|
||||
inpos++;
|
||||
str[outpos] = beginning[inpos];
|
||||
}
|
||||
if (token->identifier)
|
||||
push_string(token->identifier, str);
|
||||
free(str);
|
||||
/* If we are at the end of a quoted string, skip the ending
|
||||
* double quote. */
|
||||
if (*walk == '"')
|
||||
walk++;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "end") == 0) {
|
||||
//printf("checking for end: *%s*\n", walk);
|
||||
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
|
||||
if ((result = next_state(token)) != NULL)
|
||||
return result;
|
||||
/* To make sure we start with an appropriate matching
|
||||
* datastructure for commands which do *not* specify any
|
||||
* criteria, we re-initialize the criteria system after
|
||||
* every command. */
|
||||
// TODO: make this testable
|
||||
walk++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Having verboselog() and errorlog() is necessary when using libi3.
|
||||
*
|
||||
@ -466,6 +802,7 @@ int main(int argc, char *argv[]) {
|
||||
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);
|
||||
symbols = xcb_key_symbols_alloc(conn);
|
||||
|
@ -6,7 +6,7 @@
|
||||
# No dependencies except for perl ≥ v5.10
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings qw(FATAL utf8);
|
||||
use Data::Dumper;
|
||||
use IPC::Open2;
|
||||
use POSIX qw(locale_h);
|
||||
@ -17,25 +17,35 @@ use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use v5.10;
|
||||
use utf8;
|
||||
use open ':encoding(utf8)';
|
||||
use open ':encoding(UTF-8)';
|
||||
|
||||
binmode STDOUT, ':utf8';
|
||||
binmode STDERR, ':utf8';
|
||||
|
||||
# reads in a whole file
|
||||
sub slurp {
|
||||
open(my $fh, '<', shift) or die "$!";
|
||||
my ($filename) = @_;
|
||||
open(my $fh, '<', $filename) or die "$!";
|
||||
local $/;
|
||||
<$fh>;
|
||||
my $result;
|
||||
eval {
|
||||
$result = <$fh>;
|
||||
};
|
||||
if ($@) {
|
||||
warn "Could not read $filename: $@";
|
||||
return undef;
|
||||
} else {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
my $entry_type = 'both';
|
||||
my @entry_types;
|
||||
my $dmenu_cmd = 'dmenu -i';
|
||||
my $result = GetOptions(
|
||||
'dmenu=s' => \$dmenu_cmd,
|
||||
'entry-type=s' => \$entry_type,
|
||||
'entry-type=s' => \@entry_types,
|
||||
'version' => sub {
|
||||
say "dmenu-desktop 1.2 © 2012 Michael Stapelberg";
|
||||
say "dmenu-desktop 1.4 © 2012 Michael Stapelberg";
|
||||
exit 0;
|
||||
},
|
||||
'help' => sub {
|
||||
@ -44,6 +54,11 @@ my $result = GetOptions(
|
||||
|
||||
die "Could not parse command line options" unless $result;
|
||||
|
||||
# Filter entry types and set default type(s) if none selected
|
||||
my @valid_types = ('name', 'command', 'filename');
|
||||
@entry_types = grep { $_ ~~ @valid_types } @entry_types;
|
||||
@entry_types = ('name', 'command') unless @entry_types;
|
||||
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃
|
||||
# ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro ┃
|
||||
@ -135,7 +150,9 @@ for my $file (values %desktops) {
|
||||
# Extract all “Name” and “Exec” keys from the [Desktop Entry] group
|
||||
# and store them in $apps{$base}.
|
||||
my %names;
|
||||
my @lines = split("\n", slurp($file));
|
||||
my $content = slurp($file);
|
||||
next unless defined($content);
|
||||
my @lines = split("\n", $content);
|
||||
for my $line (@lines) {
|
||||
my $first = substr($line, 0, 1);
|
||||
next if $line eq '' || $first eq '#';
|
||||
@ -209,6 +226,13 @@ for my $app (keys %apps) {
|
||||
next if (!exists($apps{$app}->{Type}) ||
|
||||
$apps{$app}->{Type} ne 'Application');
|
||||
|
||||
# Skip broken files (Type=application, but no Exec key).
|
||||
if (!exists($apps{$app}->{Exec}) ||
|
||||
$apps{$app}->{Exec} eq '') {
|
||||
warn 'File ' . $apps{$app}->{_Location} . ' is broken: it contains Type=Application, but no Exec key/value pair.';
|
||||
next;
|
||||
}
|
||||
|
||||
# Don’t offer apps which have NoDisplay == true or Hidden == true.
|
||||
# See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries
|
||||
# for the difference between NoDisplay and Hidden.
|
||||
@ -232,7 +256,7 @@ for my $app (keys %apps) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($entry_type eq 'name' || $entry_type eq 'both') {
|
||||
if ('name' ~~ @entry_types) {
|
||||
if (exists($choices{$name})) {
|
||||
# There are two .desktop files which contain the same “Name” value.
|
||||
# I’m not sure if that is allowed to happen, but we disambiguate the
|
||||
@ -248,10 +272,25 @@ for my $app (keys %apps) {
|
||||
$choices{$name} = $app;
|
||||
}
|
||||
|
||||
if ($entry_type eq 'command' || $entry_type eq 'both') {
|
||||
if ('command' ~~ @entry_types) {
|
||||
my ($command) = split(' ', $apps{$app}->{Exec});
|
||||
|
||||
# Don’t add “geany” if “Geany” is already present.
|
||||
my @keys = map { lc } keys %choices;
|
||||
next if lc(basename($command)) ~~ @keys;
|
||||
|
||||
$choices{basename($command)} = $app;
|
||||
}
|
||||
|
||||
if ('filename' ~~ @entry_types) {
|
||||
my $filename = basename($app, '.desktop');
|
||||
|
||||
# Don’t add “geany” if “Geany” is already present.
|
||||
my @keys = map { lc } keys %choices;
|
||||
next if lc($filename) ~~ @keys;
|
||||
|
||||
$choices{$filename} = $app;
|
||||
}
|
||||
}
|
||||
|
||||
# %choices now looks like this:
|
||||
@ -282,6 +321,8 @@ my $status = ($? >> 8);
|
||||
exit $status unless $status == 0;
|
||||
|
||||
my $choice = <$dmenu_out>;
|
||||
# dmenu ≥ 4.4 adds a newline after the choice
|
||||
chomp($choice);
|
||||
my $app;
|
||||
# Exact match: the user chose “Avidemux (GTK+)”
|
||||
if (exists($choices{$choice})) {
|
||||
@ -341,6 +382,7 @@ $exec =~ s/%c/$name/g;
|
||||
# XXX: Icons are not implemented. Is the complexity (looking up the path if
|
||||
# only a name is given) actually worth it?
|
||||
#$exec =~ s/%i/--icon $icon/g;
|
||||
$exec =~ s/%i//g;
|
||||
|
||||
# location of .desktop file
|
||||
$exec =~ s/%k/$location/g;
|
||||
@ -392,7 +434,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both]
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@ -441,17 +483,18 @@ version of dmenu.
|
||||
|
||||
=item B<--entry-type=type>
|
||||
|
||||
Display the (localized) "Name" (type = name) or the command (type = command) or
|
||||
both (type = both) in dmenu.
|
||||
Display the (localized) "Name" (type = name), the command (type = command) or
|
||||
the (*.desktop) filename (type = filename) in dmenu. This option can be
|
||||
specified multiple times.
|
||||
|
||||
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
|
||||
command) and both (type = both).
|
||||
command), and "libreoffice-writer" (type = filename).
|
||||
|
||||
=back
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Version 1.2
|
||||
Version 1.4
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
|
@ -42,14 +42,19 @@ static int check_for_wrap(void) {
|
||||
/* The log wrapped. Print the remaining content and reset walk to the top
|
||||
* of the log. */
|
||||
wrap_count = header->wrap_count;
|
||||
write(STDOUT_FILENO, walk, ((logbuffer + header->offset_last_wrap) - walk));
|
||||
const int len = (logbuffer + header->offset_last_wrap) - walk;
|
||||
if (write(STDOUT_FILENO, walk, len) != len)
|
||||
err(EXIT_FAILURE, "write()");
|
||||
walk = logbuffer + sizeof(i3_shmlog_header);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_till_end(void) {
|
||||
check_for_wrap();
|
||||
int n = write(STDOUT_FILENO, walk, ((logbuffer + header->offset_next_write) - walk));
|
||||
const int len = (logbuffer + header->offset_next_write) - walk;
|
||||
const int n = write(STDOUT_FILENO, walk, len);
|
||||
if (len != n)
|
||||
err(EXIT_FAILURE, "write()");
|
||||
if (n > 0) {
|
||||
walk += n;
|
||||
}
|
||||
@ -121,7 +126,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
struct stat statbuf;
|
||||
|
||||
/* NB: While we must never read, we need O_RDWR for the pthread condvar. */
|
||||
/* NB: While we must never write, we need O_RDWR for the pthread condvar. */
|
||||
int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
|
||||
if (logbuffer_shm == -1)
|
||||
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
|
||||
@ -129,7 +134,7 @@ int main(int argc, char *argv[]) {
|
||||
if (fstat(logbuffer_shm, &statbuf) != 0)
|
||||
err(EXIT_FAILURE, "stat(%s)", shmname);
|
||||
|
||||
/* NB: While we must never read, we need O_RDWR for the pthread condvar. */
|
||||
/* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
|
||||
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
|
||||
if (logbuffer == MAP_FAILED)
|
||||
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
|
||||
|
@ -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)
|
||||
*
|
||||
* i3-input/main.c: Utility which lets the user input commands and sends them
|
||||
* to i3.
|
||||
@ -76,6 +76,24 @@ void errorlog(char *fmt, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the X11 input focus to whereever it was before.
|
||||
* This is necessary because i3-input’s window has override_redirect=1
|
||||
* (→ unmanaged by the window manager) and thus i3-input changes focus itself.
|
||||
* This function is called on exit().
|
||||
*
|
||||
*/
|
||||
static void restore_input_focus(void) {
|
||||
xcb_generic_error_t *error;
|
||||
xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
|
||||
return;
|
||||
}
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
|
||||
* rendering it (UCS-2) or sending it to i3 (UTF-8).
|
||||
@ -187,6 +205,10 @@ static void finish_input() {
|
||||
/* prefix the command if a prefix was specified on commandline */
|
||||
printf("command = %s\n", full);
|
||||
|
||||
restore_input_focus();
|
||||
|
||||
xcb_aux_sync(conn);
|
||||
|
||||
ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full);
|
||||
|
||||
#if 0
|
||||
@ -239,6 +261,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
return 1;
|
||||
}
|
||||
if (sym == XK_Escape) {
|
||||
restore_input_focus();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@ -283,24 +306,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the X11 input focus to whereever it was before.
|
||||
* This is necessary because i3-input’s window has override_redirect=1
|
||||
* (→ unmanaged by the window manager) and thus i3-input changes focus itself.
|
||||
* This function is called on exit().
|
||||
*
|
||||
*/
|
||||
static void restore_input_focus(void) {
|
||||
xcb_generic_error_t *error;
|
||||
xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
|
||||
return;
|
||||
}
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
format = strdup("%s");
|
||||
socket_path = getenv("I3SOCK");
|
||||
@ -420,7 +425,6 @@ int main(int argc, char *argv[]) {
|
||||
/* Set input focus (we have override_redirect=1, so the wm will not do
|
||||
* this for us) */
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
|
||||
atexit(restore_input_focus);
|
||||
|
||||
/* Grab the keyboard to get all input */
|
||||
xcb_flush(conn);
|
||||
@ -440,6 +444,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
|
||||
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
|
||||
restore_input_focus();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ CLEAN_TARGETS += clean-i3-msg
|
||||
i3_msg_SOURCES := $(wildcard i3-msg/*.c)
|
||||
i3_msg_HEADERS := $(wildcard i3-msg/*.h)
|
||||
i3_msg_CFLAGS = $(XCB_CFLAGS) $(PANGO_CFLAGS)
|
||||
i3_msg_LIBS = $(XCB_LIBS)
|
||||
i3_msg_LIBS = $(XCB_LIBS) $(YAJL_LIBS)
|
||||
|
||||
i3_msg_OBJECTS := $(i3_msg_SOURCES:.c=.o)
|
||||
|
||||
|
129
i3-msg/main.c
129
i3-msg/main.c
@ -28,6 +28,9 @@
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <yajl/yajl_parse.h>
|
||||
#include <yajl/yajl_version.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
||||
@ -36,6 +39,99 @@
|
||||
|
||||
static char *socket_path;
|
||||
|
||||
/*
|
||||
* Having verboselog() and errorlog() is necessary when using libi3.
|
||||
*
|
||||
*/
|
||||
void verboselog(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stdout, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void errorlog(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static char *last_key = NULL;
|
||||
|
||||
typedef struct reply_t {
|
||||
bool success;
|
||||
char *error;
|
||||
char *input;
|
||||
char *errorposition;
|
||||
} reply_t;
|
||||
|
||||
static reply_t last_reply;
|
||||
|
||||
static int reply_boolean_cb(void *params, int val) {
|
||||
if (strcmp(last_key, "success") == 0)
|
||||
last_reply.success = val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if YAJL_MAJOR >= 2
|
||||
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
|
||||
#else
|
||||
static int reply_string_cb(void *params, const unsigned char *val, unsigned int len) {
|
||||
#endif
|
||||
char *str = scalloc(len + 1);
|
||||
strncpy(str, (const char*)val, len);
|
||||
if (strcmp(last_key, "error") == 0)
|
||||
last_reply.error = str;
|
||||
else if (strcmp(last_key, "input") == 0)
|
||||
last_reply.input = str;
|
||||
else if (strcmp(last_key, "errorposition") == 0)
|
||||
last_reply.errorposition = str;
|
||||
else free(str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int reply_start_map_cb(void *params) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int reply_end_map_cb(void *params) {
|
||||
if (!last_reply.success) {
|
||||
fprintf(stderr, "ERROR: Your command: %s\n", last_reply.input);
|
||||
fprintf(stderr, "ERROR: %s\n", last_reply.errorposition);
|
||||
fprintf(stderr, "ERROR: %s\n", last_reply.error);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#if YAJL_MAJOR >= 2
|
||||
static int reply_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
|
||||
#else
|
||||
static int reply_map_key_cb(void *params, const unsigned char *keyVal, unsigned keyLen) {
|
||||
#endif
|
||||
free(last_key);
|
||||
last_key = scalloc(keyLen + 1);
|
||||
strncpy(last_key, (const char*)keyVal, keyLen);
|
||||
return 1;
|
||||
}
|
||||
|
||||
yajl_callbacks reply_callbacks = {
|
||||
NULL,
|
||||
&reply_boolean_cb,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&reply_string_cb,
|
||||
&reply_start_map_cb,
|
||||
&reply_map_key_cb,
|
||||
&reply_end_map_cb,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
socket_path = getenv("I3SOCK");
|
||||
int o, option_index = 0;
|
||||
@ -126,7 +222,7 @@ int main(int argc, char *argv[]) {
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3");
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket \"%s\"", socket_path);
|
||||
|
||||
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1)
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
@ -135,13 +231,42 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
|
||||
uint32_t reply_length;
|
||||
uint32_t reply_type;
|
||||
uint8_t *reply;
|
||||
int ret;
|
||||
if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
|
||||
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
|
||||
if (ret == -1)
|
||||
err(EXIT_FAILURE, "IPC: read()");
|
||||
exit(1);
|
||||
}
|
||||
if (reply_type != message_type)
|
||||
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
|
||||
/* For the reply of commands, have a look if that command was successful.
|
||||
* If not, nicely format the error message. */
|
||||
if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) {
|
||||
yajl_handle handle;
|
||||
#if YAJL_MAJOR < 2
|
||||
yajl_parser_config parse_conf = { 0, 0 };
|
||||
|
||||
handle = yajl_alloc(&reply_callbacks, &parse_conf, NULL, NULL);
|
||||
#else
|
||||
handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
#endif
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char*)reply, reply_length);
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
#if YAJL_MAJOR < 2
|
||||
case yajl_status_insufficient_data:
|
||||
#endif
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
|
||||
/* NB: We still fall-through and print the reply, because even if one
|
||||
* command failed, that doesn’t mean that all commands failed. */
|
||||
}
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
free(reply);
|
||||
|
||||
|
@ -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)
|
||||
*
|
||||
* i3-nagbar is a utility which displays a nag message, for example in the case
|
||||
* when the user has an error in his configuration file.
|
||||
@ -10,6 +10,7 @@
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
@ -20,6 +21,7 @@
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
@ -28,6 +30,8 @@
|
||||
#include "libi3.h"
|
||||
#include "i3-nagbar.h"
|
||||
|
||||
static char *argv0 = NULL;
|
||||
|
||||
typedef struct {
|
||||
i3String *label;
|
||||
char *action;
|
||||
@ -135,7 +139,42 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
||||
button_t *button = get_button_at(event->event_x, event->event_y);
|
||||
if (!button)
|
||||
return;
|
||||
start_application(button->action);
|
||||
|
||||
/* We need to create a custom script containing our actual command
|
||||
* since not every terminal emulator which is contained in
|
||||
* i3-sensible-terminal supports -e with multiple arguments (and not
|
||||
* all of them support -e with one quoted argument either).
|
||||
*
|
||||
* NB: The paths need to be unique, that is, don’t assume users close
|
||||
* their nagbars at any point in time (and they still need to work).
|
||||
* */
|
||||
char *script_path = get_process_filename("nagbar-cmd");
|
||||
|
||||
int fd = open(script_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1) {
|
||||
warn("Could not create temporary script to store the nagbar command");
|
||||
return;
|
||||
}
|
||||
FILE *script = fdopen(fd, "w");
|
||||
if (script == NULL) {
|
||||
warn("Could not fdopen() temporary script to store the nagbar command");
|
||||
return;
|
||||
}
|
||||
fprintf(script, "#!/bin/sh\nrm %s\n%s", script_path, button->action);
|
||||
/* Also closes fd */
|
||||
fclose(script);
|
||||
|
||||
char *terminal_cmd;
|
||||
sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", argv0);
|
||||
printf("argv0 = %s\n", argv0);
|
||||
printf("terminal_cmd = %s\n", terminal_cmd);
|
||||
|
||||
setenv("_I3_NAGBAR_CMD", script_path, 1);
|
||||
start_application(terminal_cmd);
|
||||
unsetenv("_I3_NAGBAR_CMD");
|
||||
|
||||
free(terminal_cmd);
|
||||
free(script_path);
|
||||
|
||||
/* TODO: unset flag, re-render */
|
||||
}
|
||||
@ -236,6 +275,29 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
/* The following lines are a horrible kludge. Because terminal emulators
|
||||
* have different ways of interpreting the -e command line argument (some
|
||||
* need -e "less /etc/fstab", others need -e less /etc/fstab), we need to
|
||||
* write commands to a script and then just run that script. However, since
|
||||
* on some machines, $XDG_RUNTIME_DIR and $TMPDIR are mounted with noexec,
|
||||
* we cannot directly execute the script either.
|
||||
*
|
||||
* Therefore, we run i3-nagbar instead and pass the path to the script in
|
||||
* the environment variable $_I3_NAGBAR_CMD. i3-nagbar then execs /bin/sh
|
||||
* with that path in order to run that script.
|
||||
*
|
||||
* From a security point of view, i3-nagbar is just an alias to /bin/sh in
|
||||
* certain circumstances. This should not open any new security issues, I
|
||||
* hope. */
|
||||
char *cmd = NULL;
|
||||
if ((cmd = getenv("_I3_NAGBAR_CMD")) != NULL) {
|
||||
unsetenv("_I3_NAGBAR_CMD");
|
||||
execl("/bin/sh", "/bin/sh", cmd, NULL);
|
||||
err(EXIT_FAILURE, "execv(/bin/sh, /bin/sh, %s)", cmd);
|
||||
}
|
||||
|
||||
argv0 = argv[0];
|
||||
|
||||
char *pattern = sstrdup("-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1");
|
||||
int o, option_index = 0;
|
||||
enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR;
|
||||
|
@ -43,6 +43,10 @@ struct status_block {
|
||||
blockalign_t align;
|
||||
|
||||
bool urgent;
|
||||
bool no_separator;
|
||||
|
||||
/* The amount of pixels necessary to render a separater after the block. */
|
||||
uint32_t sep_block_width;
|
||||
|
||||
/* The amount of pixels necessary to render this block. These variables are
|
||||
* only temporarily used in refresh_statusline(). */
|
||||
|
@ -28,6 +28,7 @@
|
||||
struct xcb_color_strings_t {
|
||||
char *bar_fg;
|
||||
char *bar_bg;
|
||||
char *sep_fg;
|
||||
char *active_ws_fg;
|
||||
char *active_ws_bg;
|
||||
char *active_ws_border;
|
||||
@ -88,6 +89,15 @@ void get_atoms(void);
|
||||
*/
|
||||
void kick_tray_clients(i3_output *output);
|
||||
|
||||
/*
|
||||
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
|
||||
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
|
||||
* they assume a light background.
|
||||
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
|
||||
*
|
||||
*/
|
||||
void init_tray_colors(void);
|
||||
|
||||
/*
|
||||
* Destroy the bar of the specified output
|
||||
*
|
||||
|
@ -6,6 +6,7 @@ ATOM_DO(MANAGER)
|
||||
ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION)
|
||||
ATOM_DO(_NET_SYSTEM_TRAY_VISUAL)
|
||||
ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
|
||||
ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
|
||||
ATOM_DO(_XEMBED_INFO)
|
||||
ATOM_DO(_XEMBED)
|
||||
#undef ATOM_DO
|
||||
|
@ -98,6 +98,10 @@ static int stdin_start_array(void *context) {
|
||||
static int stdin_start_map(void *context) {
|
||||
parser_ctx *ctx = context;
|
||||
memset(&(ctx->block), '\0', sizeof(struct status_block));
|
||||
|
||||
/* Default width of the separator block. */
|
||||
ctx->block.sep_block_width = 9;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -117,6 +121,9 @@ static int stdin_boolean(void *context, int val) {
|
||||
if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
|
||||
ctx->block.urgent = val;
|
||||
}
|
||||
if (strcasecmp(ctx->last_map_key, "separator") == 0) {
|
||||
ctx->block.no_separator = !val;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -140,6 +147,10 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
|
||||
} else {
|
||||
ctx->block.align = ALIGN_CENTER;
|
||||
}
|
||||
} else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
|
||||
i3String *text = i3string_from_utf8_with_length((const char *)val, len);
|
||||
ctx->block.min_width = (uint32_t)predict_text_width(text);
|
||||
i3string_free(text);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@ -153,6 +164,9 @@ static int stdin_integer(void *context, long val) {
|
||||
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
|
||||
ctx->block.min_width = (uint32_t)val;
|
||||
}
|
||||
if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
|
||||
ctx->block.sep_block_width = (uint32_t)val;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -161,6 +161,7 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
|
||||
|
||||
COLOR(statusline, bar_fg);
|
||||
COLOR(background, bar_bg);
|
||||
COLOR(separator, sep_fg);
|
||||
COLOR(focused_workspace_border, focus_ws_border);
|
||||
COLOR(focused_workspace_bg, focus_ws_bg);
|
||||
COLOR(focused_workspace_text, focus_ws_fg);
|
||||
@ -260,6 +261,7 @@ void free_colors(struct xcb_color_strings_t *colors) {
|
||||
} while (0)
|
||||
FREE_COLOR(bar_fg);
|
||||
FREE_COLOR(bar_bg);
|
||||
FREE_COLOR(sep_fg);
|
||||
FREE_COLOR(active_ws_fg);
|
||||
FREE_COLOR(active_ws_bg);
|
||||
FREE_COLOR(active_ws_border);
|
||||
|
@ -84,6 +84,8 @@ void got_bar_config(char *reply) {
|
||||
* workspaces. Everything else (creating the bars, showing the right workspace-
|
||||
* buttons and more) is taken care of by the event-drivenness of the code */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
|
||||
free_colors(&(config.colors));
|
||||
parse_config_json(reply);
|
||||
|
||||
/* Now we can actually use 'config', so let's subscribe to the appropriate
|
||||
@ -97,7 +99,6 @@ void got_bar_config(char *reply) {
|
||||
|
||||
/* Resolve color strings to colorpixels and save them, then free the strings. */
|
||||
init_colors(&(config.colors));
|
||||
free_colors(&(config.colors));
|
||||
|
||||
/* The name of this function is actually misleading. Even if no command is
|
||||
* specified, this function initiates the watchers to listen on stdin and
|
||||
|
155
i3bar/src/xcb.c
155
i3bar/src/xcb.c
@ -49,6 +49,10 @@ int screen;
|
||||
xcb_screen_t *root_screen;
|
||||
xcb_window_t xcb_root;
|
||||
|
||||
/* selection window for tray support */
|
||||
static xcb_window_t selwin = XCB_NONE;
|
||||
static xcb_intern_atom_reply_t *tray_reply = NULL;
|
||||
|
||||
/* This is needed for integration with libi3 */
|
||||
xcb_connection_t *conn;
|
||||
|
||||
@ -80,6 +84,7 @@ static mode binding;
|
||||
struct xcb_colors_t {
|
||||
uint32_t bar_fg;
|
||||
uint32_t bar_bg;
|
||||
uint32_t sep_fg;
|
||||
uint32_t active_ws_fg;
|
||||
uint32_t active_ws_bg;
|
||||
uint32_t active_ws_border;
|
||||
@ -145,7 +150,8 @@ void refresh_statusline(void) {
|
||||
|
||||
/* If this is not the last block, add some pixels for a separator. */
|
||||
if (TAILQ_NEXT(block, blocks) != NULL)
|
||||
block->width += 9;
|
||||
block->width += block->sep_block_width;
|
||||
|
||||
statusline_width += block->width + block->x_offset + block->x_append;
|
||||
}
|
||||
|
||||
@ -167,15 +173,19 @@ void refresh_statusline(void) {
|
||||
|
||||
uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg);
|
||||
set_font_colors(statusline_ctx, colorpixel, colors.bar_bg);
|
||||
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 0, block->width);
|
||||
draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 1, block->width);
|
||||
x += block->width + block->x_offset + block->x_append;
|
||||
|
||||
if (TAILQ_NEXT(block, blocks) != NULL) {
|
||||
if (TAILQ_NEXT(block, blocks) != NULL && !block->no_separator && block->sep_block_width > 0) {
|
||||
/* This is not the last block, draw a separator. */
|
||||
set_font_colors(statusline_ctx, get_colorpixel("#666666"), colors.bar_bg);
|
||||
uint32_t sep_offset = block->sep_block_width/2 + block->sep_block_width % 2;
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
|
||||
uint32_t values[] = { colors.sep_fg, colors.bar_bg };
|
||||
xcb_change_gc(xcb_connection, statusline_ctx, mask, values);
|
||||
xcb_poly_line(xcb_connection, XCB_COORD_MODE_ORIGIN, statusline_pm,
|
||||
statusline_ctx, 2,
|
||||
(xcb_point_t[]){ { x - 5, 2 }, { x - 5, font.height - 2 } });
|
||||
(xcb_point_t[]){ { x - sep_offset, 2 },
|
||||
{ x - sep_offset, font.height - 2 } });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,6 +265,7 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
|
||||
} while (0)
|
||||
PARSE_COLOR(bar_fg, "#FFFFFF");
|
||||
PARSE_COLOR(bar_bg, "#000000");
|
||||
PARSE_COLOR(sep_fg, "#666666");
|
||||
PARSE_COLOR(active_ws_fg, "#FFFFFF");
|
||||
PARSE_COLOR(active_ws_bg, "#333333");
|
||||
PARSE_COLOR(active_ws_border, "#333333");
|
||||
@ -268,6 +279,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
|
||||
PARSE_COLOR(focus_ws_bg, "#285577");
|
||||
PARSE_COLOR(focus_ws_border, "#4c7899");
|
||||
#undef PARSE_COLOR
|
||||
|
||||
init_tray_colors();
|
||||
xcb_flush(xcb_connection);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -991,6 +1005,31 @@ void init_xcb_late(char *fontname) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
|
||||
* selection.
|
||||
*
|
||||
*/
|
||||
static void send_tray_clientmessage(void) {
|
||||
uint8_t buffer[32] = { 0 };
|
||||
xcb_client_message_event_t *ev = (xcb_client_message_event_t*)buffer;
|
||||
|
||||
ev->response_type = XCB_CLIENT_MESSAGE;
|
||||
ev->window = xcb_root;
|
||||
ev->type = atoms[MANAGER];
|
||||
ev->format = 32;
|
||||
ev->data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev->data.data32[1] = tray_reply->atom;
|
||||
ev->data.data32[2] = selwin;
|
||||
|
||||
xcb_send_event(xcb_connection,
|
||||
0,
|
||||
xcb_root,
|
||||
0xFFFFFF,
|
||||
(char*)buffer);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom
|
||||
* for the X11 display we are running on, then acquiring the selection for this
|
||||
@ -1003,11 +1042,11 @@ void init_tray(void) {
|
||||
char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
|
||||
snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
|
||||
xcb_intern_atom_cookie_t tray_cookie;
|
||||
xcb_intern_atom_reply_t *tray_reply;
|
||||
if (tray_reply == NULL)
|
||||
tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
|
||||
|
||||
/* tray support: we need a window to own the selection */
|
||||
xcb_window_t selwin = xcb_generate_id(xcb_connection);
|
||||
selwin = xcb_generate_id(xcb_connection);
|
||||
uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT;
|
||||
uint32_t selval[] = { 1 };
|
||||
xcb_create_window(xcb_connection,
|
||||
@ -1016,7 +1055,7 @@ void init_tray(void) {
|
||||
xcb_root,
|
||||
-1, -1,
|
||||
1, 1,
|
||||
1,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
root_screen->root_visual,
|
||||
selmask,
|
||||
@ -1033,10 +1072,14 @@ void init_tray(void) {
|
||||
1,
|
||||
&orientation);
|
||||
|
||||
init_tray_colors();
|
||||
|
||||
if (tray_reply == NULL) {
|
||||
if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) {
|
||||
ELOG("Could not get atom %s\n", atomname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
xcb_set_selection_owner(xcb_connection,
|
||||
selwin,
|
||||
@ -1062,23 +1105,48 @@ void init_tray(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */
|
||||
void *event = scalloc(32);
|
||||
xcb_client_message_event_t *ev = event;
|
||||
ev->response_type = XCB_CLIENT_MESSAGE;
|
||||
ev->window = xcb_root;
|
||||
ev->type = atoms[MANAGER];
|
||||
ev->format = 32;
|
||||
ev->data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev->data.data32[1] = tray_reply->atom;
|
||||
ev->data.data32[2] = selwin;
|
||||
xcb_send_event(xcb_connection,
|
||||
0,
|
||||
xcb_root,
|
||||
0xFFFFFF,
|
||||
(char*)ev);
|
||||
free(event);
|
||||
free(tray_reply);
|
||||
send_tray_clientmessage();
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to set the _NET_SYSTEM_TRAY_COLORS atom on the tray selection window
|
||||
* to make GTK+ 3 applets with Symbolic Icons visible. If the colors are unset,
|
||||
* they assume a light background.
|
||||
* See also https://bugzilla.gnome.org/show_bug.cgi?id=679591
|
||||
*
|
||||
*/
|
||||
void init_tray_colors(void) {
|
||||
/* Convert colors.bar_fg (#rrggbb) to 16-bit RGB */
|
||||
const char *bar_fg = (config.colors.bar_fg ? config.colors.bar_fg : "#FFFFFF");
|
||||
|
||||
DLOG("Setting bar_fg = %s as _NET_SYSTEM_TRAY_COLORS\n", bar_fg);
|
||||
|
||||
char strgroups[3][3] = {{bar_fg[1], bar_fg[2], '\0'},
|
||||
{bar_fg[3], bar_fg[4], '\0'},
|
||||
{bar_fg[5], bar_fg[6], '\0'}};
|
||||
const uint8_t r = strtol(strgroups[0], NULL, 16);
|
||||
const uint8_t g = strtol(strgroups[1], NULL, 16);
|
||||
const uint8_t b = strtol(strgroups[2], NULL, 16);
|
||||
|
||||
const uint16_t r16 = ((uint16_t)r << 8) | r;
|
||||
const uint16_t g16 = ((uint16_t)g << 8) | g;
|
||||
const uint16_t b16 = ((uint16_t)b << 8) | b;
|
||||
|
||||
const uint32_t tray_colors[12] = {
|
||||
r16, g16, b16, /* foreground color */
|
||||
r16, g16, b16, /* error color */
|
||||
r16, g16, b16, /* warning color */
|
||||
r16, g16, b16, /* success color */
|
||||
};
|
||||
|
||||
xcb_change_property(xcb_connection,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
selwin,
|
||||
atoms[_NET_SYSTEM_TRAY_COLORS],
|
||||
XCB_ATOM_CARDINAL,
|
||||
32,
|
||||
12,
|
||||
tray_colors);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1138,6 +1206,9 @@ void get_atoms(void) {
|
||||
*
|
||||
*/
|
||||
void kick_tray_clients(i3_output *output) {
|
||||
if (TAILQ_EMPTY(output->trayclients))
|
||||
return;
|
||||
|
||||
trayclient *trayclient;
|
||||
while (!TAILQ_EMPTY(output->trayclients)) {
|
||||
trayclient = TAILQ_FIRST(output->trayclients);
|
||||
@ -1153,6 +1224,20 @@ void kick_tray_clients(i3_output *output) {
|
||||
* event afterwards, but better safe than sorry. */
|
||||
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
||||
}
|
||||
|
||||
/* Fake a DestroyNotify so that Qt re-adds tray icons.
|
||||
* We cannot actually destroy the window because then Qt will not restore
|
||||
* its event mask on the new window. */
|
||||
uint8_t buffer[32] = { 0 };
|
||||
xcb_destroy_notify_event_t *event = (xcb_destroy_notify_event_t*)buffer;
|
||||
|
||||
event->response_type = XCB_DESTROY_NOTIFY;
|
||||
event->event = selwin;
|
||||
event->window = selwin;
|
||||
|
||||
xcb_send_event(conn, false, selwin, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)event);
|
||||
|
||||
send_tray_clientmessage();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1261,7 +1346,7 @@ void reconfig_windows(void) {
|
||||
xcb_root,
|
||||
walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
|
||||
walk->rect.w, font.height + 6,
|
||||
1,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
root_screen->root_visual,
|
||||
mask,
|
||||
@ -1398,7 +1483,7 @@ void reconfig_windows(void) {
|
||||
values[3] = font.height + 6;
|
||||
values[4] = XCB_STACK_MODE_ABOVE;
|
||||
|
||||
DLOG("Destroying buffer for output %s", walk->name);
|
||||
DLOG("Destroying buffer for output %s\n", walk->name);
|
||||
xcb_free_pixmap(xcb_connection, walk->buffer);
|
||||
|
||||
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
|
||||
@ -1407,7 +1492,7 @@ void reconfig_windows(void) {
|
||||
mask,
|
||||
values);
|
||||
|
||||
DLOG("Recreating buffer for output %s", walk->name);
|
||||
DLOG("Recreating buffer for output %s\n", walk->name);
|
||||
xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
|
||||
root_screen->root_depth,
|
||||
walk->buffer,
|
||||
@ -1431,7 +1516,7 @@ void reconfig_windows(void) {
|
||||
*/
|
||||
void draw_bars(bool unhide) {
|
||||
DLOG("Drawing Bars...\n");
|
||||
int i = 0;
|
||||
int i = 1;
|
||||
|
||||
refresh_statusline();
|
||||
|
||||
@ -1530,7 +1615,7 @@ void draw_bars(bool unhide) {
|
||||
outputs_walk->bargc,
|
||||
mask,
|
||||
vals_border);
|
||||
xcb_rectangle_t rect_border = { i, 0, ws_walk->name_width + 10, font.height + 4 };
|
||||
xcb_rectangle_t rect_border = { i, 1, ws_walk->name_width + 10, font.height + 4 };
|
||||
xcb_poly_fill_rectangle(xcb_connection,
|
||||
outputs_walk->buffer,
|
||||
outputs_walk->bargc,
|
||||
@ -1541,14 +1626,14 @@ void draw_bars(bool unhide) {
|
||||
outputs_walk->bargc,
|
||||
mask,
|
||||
vals);
|
||||
xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 2 };
|
||||
xcb_rectangle_t rect = { i + 1, 2, ws_walk->name_width + 8, font.height + 2 };
|
||||
xcb_poly_fill_rectangle(xcb_connection,
|
||||
outputs_walk->buffer,
|
||||
outputs_walk->bargc,
|
||||
1,
|
||||
&rect);
|
||||
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
|
||||
draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, 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;
|
||||
|
||||
}
|
||||
@ -1564,7 +1649,7 @@ void draw_bars(bool unhide) {
|
||||
outputs_walk->bargc,
|
||||
mask,
|
||||
vals_border);
|
||||
xcb_rectangle_t rect_border = { i, 0, binding.width + 10, font.height + 4 };
|
||||
xcb_rectangle_t rect_border = { i, 1, binding.width + 10, font.height + 4 };
|
||||
xcb_poly_fill_rectangle(xcb_connection,
|
||||
outputs_walk->buffer,
|
||||
outputs_walk->bargc,
|
||||
@ -1576,7 +1661,7 @@ void draw_bars(bool unhide) {
|
||||
outputs_walk->bargc,
|
||||
mask,
|
||||
vals);
|
||||
xcb_rectangle_t rect = { i + 1, 1, binding.width + 8, font.height + 2 };
|
||||
xcb_rectangle_t rect = { i + 1, 2, binding.width + 8, font.height + 2 };
|
||||
xcb_poly_fill_rectangle(xcb_connection,
|
||||
outputs_walk->buffer,
|
||||
outputs_walk->bargc,
|
||||
@ -1584,7 +1669,7 @@ void draw_bars(bool unhide) {
|
||||
&rect);
|
||||
|
||||
set_font_colors(outputs_walk->bargc, fg_color, bg_color);
|
||||
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, binding.width);
|
||||
draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, binding.width);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
@ -2,6 +2,7 @@ xmacro(_NET_SUPPORTED)
|
||||
xmacro(_NET_SUPPORTING_WM_CHECK)
|
||||
xmacro(_NET_WM_NAME)
|
||||
xmacro(_NET_WM_STATE_FULLSCREEN)
|
||||
xmacro(_NET_WM_STATE_DEMANDS_ATTENTION)
|
||||
xmacro(_NET_WM_STATE)
|
||||
xmacro(_NET_WM_WINDOW_TYPE)
|
||||
xmacro(_NET_WM_WINDOW_TYPE_DOCK)
|
||||
|
@ -324,6 +324,12 @@ bool con_has_urgent_child(Con *con);
|
||||
*/
|
||||
void con_update_parents_urgency(Con *con);
|
||||
|
||||
/**
|
||||
* Set urgency flag to the container, all the parent containers and the workspace.
|
||||
*
|
||||
*/
|
||||
void con_set_urgency(Con *con, bool urgent);
|
||||
|
||||
/**
|
||||
* Create a string representing the subtree under con.
|
||||
*
|
||||
|
@ -6,8 +6,8 @@
|
||||
*
|
||||
* include/config.h: Contains all structs/variables for the configurable
|
||||
* part of i3 as well as functions handling the configuration file (calling
|
||||
* the parser (src/cfgparse.y) with the correct path, switching key bindings
|
||||
* mode).
|
||||
* the parser (src/config_parse.c) with the correct path, switching key
|
||||
* bindings mode).
|
||||
*
|
||||
*/
|
||||
#ifndef I3_CONFIG_H
|
||||
@ -24,8 +24,6 @@ extern char *current_configpath;
|
||||
extern Config config;
|
||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
||||
/* defined in src/cfgparse.y */
|
||||
extern bool force_old_config_parser;
|
||||
|
||||
/**
|
||||
* Used during the config file lexing/parsing to keep the state of the lexer
|
||||
@ -269,6 +267,7 @@ struct Barconfig {
|
||||
struct bar_colors {
|
||||
char *background;
|
||||
char *statusline;
|
||||
char *separator;
|
||||
|
||||
char *focused_workspace_border;
|
||||
char *focused_workspace_bg;
|
||||
@ -342,7 +341,4 @@ Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode
|
||||
*/
|
||||
void kill_configerror_nagbar(bool wait_for_it);
|
||||
|
||||
/* prototype for src/cfgparse.y */
|
||||
void parse_file(const char *f);
|
||||
|
||||
#endif
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
extern pid_t config_error_nagbar_pid;
|
||||
|
||||
/*
|
||||
* The result of a parse_config call. Currently unused, but the JSON output
|
||||
* will be useful in the future when we implement a config parsing IPC command.
|
||||
@ -29,4 +31,11 @@ struct ConfigResult {
|
||||
|
||||
struct ConfigResult *parse_config(const char *input, struct context *context);
|
||||
|
||||
/**
|
||||
* Parses the given file by first replacing the variables, then calling
|
||||
* parse_config and possibly launching i3-nagbar.
|
||||
*
|
||||
*/
|
||||
void parse_file(const char *f);
|
||||
|
||||
#endif
|
||||
|
@ -196,7 +196,8 @@ struct regex {
|
||||
|
||||
/**
|
||||
* Holds a keybinding, consisting of a keycode combined with modifiers and the
|
||||
* command which is executed as soon as the key is pressed (see src/cfgparse.y)
|
||||
* command which is executed as soon as the key is pressed (see
|
||||
* src/config_parser.c)
|
||||
*
|
||||
*/
|
||||
struct Binding {
|
||||
@ -569,8 +570,14 @@ struct Con {
|
||||
void(*on_remove_child)(Con *);
|
||||
|
||||
enum {
|
||||
/* Not a scratchpad window. */
|
||||
SCRATCHPAD_NONE = 0,
|
||||
|
||||
/* Just moved to scratchpad, not resized by the user yet.
|
||||
* Window will be auto-centered and sized appropriately. */
|
||||
SCRATCHPAD_FRESH = 1,
|
||||
|
||||
/* The user changed position/size of the scratchpad window. */
|
||||
SCRATCHPAD_CHANGED = 2
|
||||
} scratchpad_state;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* This public header defines the different constants and message types to use
|
||||
* for the IPC interface to i3 (see docs/ipc for more information).
|
||||
@ -11,6 +11,15 @@
|
||||
#ifndef I3_I3_IPC_H
|
||||
#define I3_I3_IPC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct i3_ipc_header {
|
||||
/* 6 = strlen(I3_IPC_MAGIC) */
|
||||
char magic[6];
|
||||
uint32_t size;
|
||||
uint32_t type;
|
||||
} __attribute__ ((packed)) i3_ipc_header_t;
|
||||
|
||||
/*
|
||||
* Messages from clients to i3
|
||||
*
|
||||
@ -87,4 +96,7 @@
|
||||
/* The output event will be triggered upon mode changes */
|
||||
#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2)
|
||||
|
||||
/* The window event will be triggered upon window changes */
|
||||
#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3)
|
||||
|
||||
#endif
|
||||
|
@ -10,6 +10,8 @@
|
||||
#ifndef I3_KEY_PRESS_H
|
||||
#define I3_KEY_PRESS_H
|
||||
|
||||
extern pid_t command_error_nagbar_pid;
|
||||
|
||||
/**
|
||||
* There was a key press. We compare this key code with our bindings table and pass
|
||||
* the bound action to parse_command().
|
||||
|
@ -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)
|
||||
*
|
||||
* libi3: contains functions which are used by i3 *and* accompanying tools such
|
||||
* as i3-msg, i3-config-wizard, …
|
||||
@ -47,6 +47,9 @@ struct Font {
|
||||
/** The height of the font, built from font_ascent + font_descent */
|
||||
int height;
|
||||
|
||||
/** The pattern/name used to load the font. */
|
||||
char *pattern;
|
||||
|
||||
union {
|
||||
struct {
|
||||
/** The xcb-id for the font */
|
||||
@ -202,8 +205,8 @@ int ipc_connect(const char *socket_path);
|
||||
* Returns 0 on success.
|
||||
*
|
||||
*/
|
||||
int ipc_send_message(int sockfd, uint32_t message_size,
|
||||
uint32_t message_type, const uint8_t *payload);
|
||||
int ipc_send_message(int sockfd, const uint32_t message_size,
|
||||
const uint32_t message_type, const uint8_t *payload);
|
||||
|
||||
/**
|
||||
* Reads a message from the given socket file descriptor and stores its length
|
||||
@ -216,7 +219,7 @@ int ipc_send_message(int sockfd, uint32_t message_size,
|
||||
* Returns 0 on success.
|
||||
*
|
||||
*/
|
||||
int ipc_recv_message(int sockfd, uint32_t message_type,
|
||||
int ipc_recv_message(int sockfd, uint32_t *message_type,
|
||||
uint32_t *reply_length, uint8_t **reply);
|
||||
|
||||
/**
|
||||
@ -355,4 +358,10 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen);
|
||||
*/
|
||||
bool is_debug_build() __attribute__((const));
|
||||
|
||||
/**
|
||||
* Returns the name of a temporary file with the specified prefix.
|
||||
*
|
||||
*/
|
||||
char *get_process_filename(const char *prefix);
|
||||
|
||||
#endif
|
||||
|
@ -87,20 +87,29 @@ Output *get_output_by_name(const char *name);
|
||||
*/
|
||||
Output *get_output_containing(int x, int y);
|
||||
|
||||
/**
|
||||
* Gets the output which is the last one in the given direction, for example
|
||||
* the output on the most bottom when direction == D_DOWN, the output most
|
||||
* right when direction == D_RIGHT and so on.
|
||||
*
|
||||
* This function always returns a output.
|
||||
*
|
||||
*/
|
||||
Output *get_output_most(direction_t direction, Output *current);
|
||||
|
||||
/**
|
||||
* Gets the output which is the next one in the given direction.
|
||||
*
|
||||
* If close_far == CLOSEST_OUTPUT, then the output next to the current one will
|
||||
* selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
|
||||
* in the given direction will be selected.
|
||||
*
|
||||
* NULL will be returned when no active outputs are present in the direction
|
||||
* specified (note that ‘current’ counts as such an output).
|
||||
*
|
||||
*/
|
||||
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
|
||||
|
||||
/**
|
||||
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
|
||||
*
|
||||
* For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
|
||||
* get_output_next_wrap(D_DOWN, x) will return the topmost output.
|
||||
*
|
||||
* This function always returns a output: if no active outputs can be found,
|
||||
* current itself is returned.
|
||||
*
|
||||
*/
|
||||
Output *get_output_next_wrap(direction_t direction, Output *current);
|
||||
|
||||
#endif
|
||||
|
@ -21,4 +21,9 @@
|
||||
*/
|
||||
void render_con(Con *con, bool render_fullscreen);
|
||||
|
||||
/*
|
||||
* Returns the height for the decorations
|
||||
*/
|
||||
int render_deco_height(void);
|
||||
|
||||
#endif
|
||||
|
@ -105,13 +105,6 @@ char *resolve_tilde(const char *path);
|
||||
*/
|
||||
bool path_exists(const char *path);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of a temporary file with the specified prefix.
|
||||
*
|
||||
*/
|
||||
char *get_process_filename(const char *prefix);
|
||||
|
||||
/**
|
||||
* Restart i3 in-place
|
||||
* appends -a to argument list to disable autostart
|
||||
@ -130,4 +123,23 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Starts an i3-nagbar instance with the given parameters. Takes care of
|
||||
* handling SIGCHLD and killing i3-nagbar when i3 exits.
|
||||
*
|
||||
* The resulting PID will be stored in *nagbar_pid and can be used with
|
||||
* kill_nagbar() to kill the bar later on.
|
||||
*
|
||||
*/
|
||||
void start_nagbar(pid_t *nagbar_pid, char *argv[]);
|
||||
|
||||
/**
|
||||
* Kills the i3-nagbar process, if *nagbar_pid != -1.
|
||||
*
|
||||
* If wait_for_it is set (restarting i3), this function will waitpid(),
|
||||
* otherwise, ev is assumed to handle it (reloading).
|
||||
*
|
||||
*/
|
||||
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
|
||||
|
||||
#endif
|
||||
|
@ -45,6 +45,15 @@
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | /* …subwindows get notifies */ \
|
||||
XCB_EVENT_MASK_ENTER_WINDOW) /* …user moves cursor inside our window */
|
||||
|
||||
#define ROOT_EVENT_MASK (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | \
|
||||
XCB_EVENT_MASK_BUTTON_PRESS | \
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video \
|
||||
projector), the root window gets a \
|
||||
ConfigureNotify */ \
|
||||
XCB_EVENT_MASK_POINTER_MOTION | \
|
||||
XCB_EVENT_MASK_PROPERTY_CHANGE | \
|
||||
XCB_EVENT_MASK_ENTER_WINDOW)
|
||||
|
||||
#define xmacro(atom) xcb_atom_t A_ ## atom;
|
||||
#include "atoms.xmacro"
|
||||
#undef xmacro
|
||||
|
16
libi3/font.c
16
libi3/font.c
@ -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)
|
||||
*
|
||||
*/
|
||||
#include <assert.h>
|
||||
@ -143,14 +143,18 @@ i3Font load_font(const char *pattern, const bool fallback) {
|
||||
#if PANGO_SUPPORT
|
||||
/* Try to load a pango font if specified */
|
||||
if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {
|
||||
pattern += strlen("pango:");
|
||||
if (load_pango_font(&font, pattern))
|
||||
const char *font_pattern = pattern + strlen("pango:");
|
||||
if (load_pango_font(&font, font_pattern)) {
|
||||
font.pattern = sstrdup(pattern);
|
||||
return font;
|
||||
}
|
||||
} else if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) {
|
||||
pattern += strlen("xft:");
|
||||
if (load_pango_font(&font, pattern))
|
||||
const char *font_pattern = pattern + strlen("xft:");
|
||||
if (load_pango_font(&font, font_pattern)) {
|
||||
font.pattern = sstrdup(pattern);
|
||||
return font;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Send all our requests first */
|
||||
@ -189,6 +193,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
|
||||
}
|
||||
}
|
||||
|
||||
font.pattern = sstrdup(pattern);
|
||||
LOG("Using X font %s\n", pattern);
|
||||
|
||||
/* Get information (height/name) for this font */
|
||||
@ -222,6 +227,7 @@ void set_font(i3Font *font) {
|
||||
*
|
||||
*/
|
||||
void free_font(void) {
|
||||
free(savedFont->pattern);
|
||||
switch (savedFont->type) {
|
||||
case FONT_TYPE_NONE:
|
||||
/* Nothing to do */
|
||||
|
58
libi3/get_process_filename.c
Normal file
58
libi3/get_process_filename.c
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <err.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libi3.h"
|
||||
|
||||
/*
|
||||
* Returns the name of a temporary file with the specified prefix.
|
||||
*
|
||||
*/
|
||||
char *get_process_filename(const char *prefix) {
|
||||
/* dir stores the directory path for this and all subsequent calls so that
|
||||
* we only create a temporary directory once per i3 instance. */
|
||||
static char *dir = NULL;
|
||||
if (dir == NULL) {
|
||||
/* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
|
||||
if ((dir = getenv("XDG_RUNTIME_DIR"))) {
|
||||
char *tmp;
|
||||
sasprintf(&tmp, "%s/i3", dir);
|
||||
dir = tmp;
|
||||
struct stat buf;
|
||||
if (stat(dir, &buf) != 0) {
|
||||
if (mkdir(dir, 0700) == -1) {
|
||||
perror("mkdir()");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* If not, we create a (secure) temp directory using the template
|
||||
* /tmp/i3-<user>.XXXXXX */
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *username = pw ? pw->pw_name : "unknown";
|
||||
sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
|
||||
/* mkdtemp modifies dir */
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
perror("mkdtemp()");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
char *filename;
|
||||
sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
|
||||
return filename;
|
||||
}
|
@ -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)
|
||||
*
|
||||
*/
|
||||
#include <string.h>
|
||||
@ -10,6 +10,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <i3/ipc.h>
|
||||
|
||||
@ -20,57 +21,56 @@
|
||||
* (reply_length) as well as a pointer to its contents (reply).
|
||||
*
|
||||
* Returns -1 when read() fails, errno will remain.
|
||||
* Returns -2 when the IPC protocol is violated (invalid magic, unexpected
|
||||
* Returns -2 on EOF.
|
||||
* Returns -3 when the IPC protocol is violated (invalid magic, unexpected
|
||||
* message type, EOF instead of a message). Additionally, the error will be
|
||||
* printed to stderr.
|
||||
* Returns 0 on success.
|
||||
*
|
||||
*/
|
||||
int ipc_recv_message(int sockfd, uint32_t message_type,
|
||||
int ipc_recv_message(int sockfd, uint32_t *message_type,
|
||||
uint32_t *reply_length, uint8_t **reply) {
|
||||
/* Read the message header first */
|
||||
uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
|
||||
const uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
|
||||
char msg[to_read];
|
||||
char *walk = msg;
|
||||
|
||||
uint32_t read_bytes = 0;
|
||||
while (read_bytes < to_read) {
|
||||
int n = read(sockfd, msg + read_bytes, to_read);
|
||||
int n = read(sockfd, msg + read_bytes, to_read - read_bytes);
|
||||
if (n == -1)
|
||||
return -1;
|
||||
if (n == 0) {
|
||||
fprintf(stderr, "IPC: received EOF instead of reply\n");
|
||||
ELOG("IPC: received EOF instead of reply\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
read_bytes += n;
|
||||
to_read -= n;
|
||||
}
|
||||
|
||||
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
|
||||
fprintf(stderr, "IPC: invalid magic in reply\n");
|
||||
return -2;
|
||||
ELOG("IPC: invalid magic in reply\n");
|
||||
return -3;
|
||||
}
|
||||
|
||||
walk += strlen(I3_IPC_MAGIC);
|
||||
*reply_length = *((uint32_t*)walk);
|
||||
walk += sizeof(uint32_t);
|
||||
if (*((uint32_t*)walk) != message_type) {
|
||||
fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type);
|
||||
return -2;
|
||||
}
|
||||
if (message_type != NULL)
|
||||
*message_type = *((uint32_t*)walk);
|
||||
|
||||
*reply = smalloc(*reply_length);
|
||||
|
||||
to_read = *reply_length;
|
||||
read_bytes = 0;
|
||||
while (read_bytes < to_read) {
|
||||
int n = read(sockfd, *reply + read_bytes, to_read);
|
||||
if (n == -1)
|
||||
int n;
|
||||
while (read_bytes < *reply_length) {
|
||||
if ((n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes)) == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
read_bytes += n;
|
||||
to_read -= n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -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)
|
||||
*
|
||||
*/
|
||||
#include <string.h>
|
||||
@ -24,24 +24,35 @@
|
||||
* Returns 0 on success.
|
||||
*
|
||||
*/
|
||||
int ipc_send_message(int sockfd, uint32_t message_size,
|
||||
uint32_t message_type, const uint8_t *payload) {
|
||||
int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
|
||||
char msg[buffer_size];
|
||||
char *walk = msg;
|
||||
|
||||
strncpy(walk, I3_IPC_MAGIC, buffer_size - 1);
|
||||
walk += strlen(I3_IPC_MAGIC);
|
||||
memcpy(walk, &message_size, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
memcpy(walk, &message_type, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
memcpy(walk, payload, message_size);
|
||||
int ipc_send_message(int sockfd, const uint32_t message_size,
|
||||
const uint32_t message_type, const uint8_t *payload) {
|
||||
const i3_ipc_header_t header = {
|
||||
/* We don’t use I3_IPC_MAGIC because it’s a 0-terminated C string. */
|
||||
.magic = { 'i', '3', '-', 'i', 'p', 'c' },
|
||||
.size = message_size,
|
||||
.type = message_type
|
||||
};
|
||||
|
||||
int sent_bytes = 0;
|
||||
while (sent_bytes < buffer_size) {
|
||||
int n = write(sockfd, msg + sent_bytes, buffer_size - sent_bytes);
|
||||
if (n == -1) {
|
||||
int n = 0;
|
||||
|
||||
/* This first loop is basically unnecessary. No operating system has
|
||||
* buffers which cannot fit 14 bytes into them, so the write() will only be
|
||||
* called once. */
|
||||
while (sent_bytes < sizeof(i3_ipc_header_t)) {
|
||||
if ((n = write(sockfd, ((void*)&header) + sent_bytes, sizeof(i3_ipc_header_t) - sent_bytes)) == -1) {
|
||||
if (errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sent_bytes += n;
|
||||
}
|
||||
|
||||
sent_bytes = 0;
|
||||
|
||||
while (sent_bytes < message_size) {
|
||||
if ((n = write(sockfd, payload + sent_bytes, message_size - sent_bytes)) == -1) {
|
||||
if (errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
|
@ -7,7 +7,7 @@ template::[header-declarations]
|
||||
<refentrytitle>{mantitle}</refentrytitle>
|
||||
<manvolnum>{manvolnum}</manvolnum>
|
||||
<refmiscinfo class="source">i3</refmiscinfo>
|
||||
<refmiscinfo class="version">4.4</refmiscinfo>
|
||||
<refmiscinfo class="version">4.5</refmiscinfo>
|
||||
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
|
||||
</refmeta>
|
||||
<refnamediv>
|
||||
|
@ -229,7 +229,7 @@ state RESTART_STATE:
|
||||
|
||||
# popup_during_fullscreen
|
||||
state POPUP_DURING_FULLSCREEN:
|
||||
value = 'ignore', 'leave_fullscreen'
|
||||
value = 'ignore', 'leave_fullscreen', 'smart'
|
||||
-> call cfg_popup_during_fullscreen($value)
|
||||
|
||||
# client.background <hexcolor>
|
||||
@ -272,7 +272,7 @@ state FONT:
|
||||
state BINDING:
|
||||
release = '--release'
|
||||
->
|
||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
|
||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
|
||||
->
|
||||
'+'
|
||||
->
|
||||
@ -317,7 +317,7 @@ state MODE_IGNORE_LINE:
|
||||
state MODE_BINDING:
|
||||
release = '--release'
|
||||
->
|
||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch'
|
||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
|
||||
->
|
||||
'+'
|
||||
->
|
||||
@ -419,7 +419,7 @@ state BAR_COLORS:
|
||||
end ->
|
||||
'#' -> BAR_COLORS_IGNORE_LINE
|
||||
'set' -> BAR_COLORS_IGNORE_LINE
|
||||
colorclass = 'background', 'statusline'
|
||||
colorclass = 'background', 'statusline', 'separator'
|
||||
-> BAR_COLORS_SINGLE
|
||||
colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace'
|
||||
-> BAR_COLORS_BORDER
|
||||
|
301
src/cfgparse.l
301
src/cfgparse.l
@ -1,301 +0,0 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
*/
|
||||
%option nounput
|
||||
%option noinput
|
||||
%option noyy_top_state
|
||||
%option stack
|
||||
|
||||
%{
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "data.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
#include "libi3.h"
|
||||
|
||||
#include "cfgparse.tab.h"
|
||||
|
||||
int yycolumn = 1;
|
||||
|
||||
#define YY_DECL int yylex (struct context *context)
|
||||
|
||||
#define YY_USER_ACTION { \
|
||||
context->first_column = yycolumn; \
|
||||
context->last_column = yycolumn+yyleng-1; \
|
||||
yycolumn += yyleng; \
|
||||
}
|
||||
|
||||
/* macro to first eat whitespace, then expect a string */
|
||||
#define WS_STRING do { \
|
||||
yy_push_state(WANT_STRING); \
|
||||
yy_push_state(EAT_WHITESPACE); \
|
||||
} while (0)
|
||||
|
||||
#define BAR_TRIPLE_COLOR do { \
|
||||
yy_push_state(BAR_COLOR); \
|
||||
yy_push_state(BAR_COLOR); \
|
||||
yy_push_state(BAR_COLOR); \
|
||||
} while (0)
|
||||
|
||||
%}
|
||||
|
||||
EOL (\r?\n)
|
||||
|
||||
%s WANT_STRING
|
||||
%s WANT_QSTRING
|
||||
%s BINDSYM_COND
|
||||
%s ASSIGN_COND
|
||||
%s ASSIGN_TARGET_COND
|
||||
%s COLOR_COND
|
||||
%s OUTPUT_COND
|
||||
%s FOR_WINDOW_COND
|
||||
%s EAT_WHITESPACE
|
||||
%s BORDER_WIDTH
|
||||
|
||||
%x BUFFER_LINE
|
||||
%x BAR
|
||||
%x BAR_MODE
|
||||
%x BAR_MODIFIER
|
||||
%x BAR_POSITION
|
||||
%x BAR_COLORS
|
||||
%x BAR_COLOR
|
||||
|
||||
%x EXEC
|
||||
%x OPTRELEASE
|
||||
|
||||
%%
|
||||
|
||||
{
|
||||
/* This is called when a new line is lexed. We only want the
|
||||
* first line to match to go into state BUFFER_LINE */
|
||||
if (context->line_number == 0) {
|
||||
context->line_number = 1;
|
||||
BEGIN(INITIAL);
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
|
||||
/* save whole line */
|
||||
context->line_copy = sstrdup(yytext);
|
||||
|
||||
yyless(0);
|
||||
yy_pop_state();
|
||||
yy_set_bol(true);
|
||||
yycolumn = 1;
|
||||
}
|
||||
|
||||
/* This part of the lexer handles the bar {} blocks */
|
||||
<BAR,BAR_MODE,BAR_MODIFIER,BAR_POSITION,BAR_COLORS,BAR_COLOR>[ \t]+ { /* ignore whitespace */ ; }
|
||||
<BAR>"{" { return '{'; }
|
||||
<BAR>"}" { yy_pop_state(); return '}'; }
|
||||
<BAR>^[ \t]*#[^\n]* { return TOKCOMMENT; }
|
||||
<BAR>output { WS_STRING; return TOK_BAR_OUTPUT; }
|
||||
<BAR>tray_output { WS_STRING; return TOK_BAR_TRAY_OUTPUT; }
|
||||
<BAR>socket_path { WS_STRING; return TOK_BAR_SOCKET_PATH; }
|
||||
<BAR>mode { yy_push_state(BAR_MODE); return TOK_BAR_MODE; }
|
||||
<BAR_MODE>hide { yy_pop_state(); return TOK_BAR_HIDE; }
|
||||
<BAR_MODE>dock { yy_pop_state(); return TOK_BAR_DOCK; }
|
||||
<BAR>modifier { yy_push_state(BAR_MODIFIER); return TOK_BAR_MODIFIER; }
|
||||
<BAR_MODIFIER>control { yy_pop_state(); return TOK_BAR_CONTROL; }
|
||||
<BAR_MODIFIER>ctrl { yy_pop_state(); return TOK_BAR_CONTROL; }
|
||||
<BAR_MODIFIER>shift { yy_pop_state(); return TOK_BAR_SHIFT; }
|
||||
<BAR_MODIFIER>Mod1 { yy_pop_state(); return TOK_BAR_MOD1; }
|
||||
<BAR_MODIFIER>Mod2 { yy_pop_state(); return TOK_BAR_MOD2; }
|
||||
<BAR_MODIFIER>Mod3 { yy_pop_state(); return TOK_BAR_MOD3; }
|
||||
<BAR_MODIFIER>Mod4 { yy_pop_state(); return TOK_BAR_MOD4; }
|
||||
<BAR_MODIFIER>Mod5 { yy_pop_state(); return TOK_BAR_MOD5; }
|
||||
<BAR>position { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; }
|
||||
<BAR_POSITION>bottom { yy_pop_state(); return TOK_BAR_BOTTOM; }
|
||||
<BAR_POSITION>top { yy_pop_state(); return TOK_BAR_TOP; }
|
||||
<BAR>status_command { WS_STRING; return TOK_BAR_STATUS_COMMAND; }
|
||||
<BAR>i3bar_command { WS_STRING; return TOK_BAR_I3BAR_COMMAND; }
|
||||
<BAR>font { WS_STRING; return TOK_BAR_FONT; }
|
||||
<BAR>workspace_buttons { return TOK_BAR_WORKSPACE_BUTTONS; }
|
||||
<BAR>verbose { return TOK_BAR_VERBOSE; }
|
||||
<BAR>colors { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; }
|
||||
<BAR_COLORS>"{" { return '{'; }
|
||||
<BAR_COLORS>"}" { yy_pop_state(); return '}'; }
|
||||
<BAR_COLORS>^[ \t]*#[^\n]* { return TOKCOMMENT; }
|
||||
<BAR_COLORS>background { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; }
|
||||
<BAR_COLORS>statusline { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; }
|
||||
<BAR_COLORS>focused_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; }
|
||||
<BAR_COLORS>active_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; }
|
||||
<BAR_COLORS>inactive_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; }
|
||||
<BAR_COLORS>urgent_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; }
|
||||
<BAR_COLOR>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
|
||||
<BAR_COLOR>{EOL} {
|
||||
yy_pop_state();
|
||||
FREE(context->line_copy);
|
||||
context->line_number++;
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
<BAR,BAR_COLORS,BAR_MODE,BAR_MODIFIER,BAR_POSITION>[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
|
||||
|
||||
|
||||
|
||||
<FOR_WINDOW_COND>"]" { yy_pop_state(); return ']'; }
|
||||
<ASSIGN_COND>"[" {
|
||||
/* this is the case for the new assign syntax
|
||||
* that uses criteria */
|
||||
yy_pop_state();
|
||||
yy_push_state(FOR_WINDOW_COND);
|
||||
/* afterwards we will be in ASSIGN_TARGET_COND */
|
||||
return '[';
|
||||
}
|
||||
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
|
||||
<EAT_WHITESPACE>{EOL} { yy_pop_state(); }
|
||||
<BINDSYM_COND>{EOL} { yy_pop_state(); }
|
||||
<WANT_QSTRING>\"[^\"]+\" {
|
||||
yy_pop_state();
|
||||
/* strip quotes */
|
||||
char *copy = sstrdup(yytext+1);
|
||||
copy[strlen(copy)-1] = '\0';
|
||||
yylval.string = copy;
|
||||
return STR;
|
||||
}
|
||||
<WANT_STRING>[^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; }
|
||||
<OUTPUT_COND>[a-zA-Z0-9\/_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; }
|
||||
^[ \t]*#[^\n]* { return TOKCOMMENT; }
|
||||
<COLOR_COND>#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; }
|
||||
<COLOR_COND>{EOL} {
|
||||
yy_pop_state();
|
||||
FREE(context->line_copy);
|
||||
context->line_number++;
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
<ASSIGN_TARGET_COND>[ \t]*→[ \t]* { BEGIN(WANT_STRING); }
|
||||
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
|
||||
<BORDER_WIDTH>[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
|
||||
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
|
||||
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
|
||||
<OPTRELEASE>--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
|
||||
<OPTRELEASE>. { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
|
||||
[0-9-]+ { yylval.number = atoi(yytext); return NUMBER; }
|
||||
bar { yy_push_state(BAR); return TOK_BAR; }
|
||||
mode { return TOKMODE; }
|
||||
bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
|
||||
bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; }
|
||||
bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; }
|
||||
floating_maximum_size { return TOKFLOATING_MAXIMUM_SIZE; }
|
||||
floating_minimum_size { return TOKFLOATING_MINIMUM_SIZE; }
|
||||
floating_modifier { return TOKFLOATING_MODIFIER; }
|
||||
workspace { return TOKWORKSPACE; }
|
||||
output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; }
|
||||
terminal { WS_STRING; return TOKTERMINAL; }
|
||||
font { WS_STRING; return TOKFONT; }
|
||||
assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; }
|
||||
set[^\n]* { return TOKCOMMENT; }
|
||||
ipc-socket { WS_STRING; return TOKIPCSOCKET; }
|
||||
ipc_socket { WS_STRING; return TOKIPCSOCKET; }
|
||||
restart_state { WS_STRING; return TOKRESTARTSTATE; }
|
||||
default_orientation { return TOK_ORIENTATION; }
|
||||
horizontal { return TOK_HORIZ; }
|
||||
vertical { return TOK_VERT; }
|
||||
auto { return TOK_AUTO; }
|
||||
workspace_layout { return TOK_WORKSPACE_LAYOUT; }
|
||||
new_window { return TOKNEWWINDOW; }
|
||||
new_float { return TOKNEWFLOAT; }
|
||||
normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
|
||||
none { return TOK_NONE; }
|
||||
1pixel { return TOK_1PIXEL; }
|
||||
pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
|
||||
hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; }
|
||||
both { return TOK_BOTH; }
|
||||
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
|
||||
force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; }
|
||||
force_xinerama { return TOK_FORCE_XINERAMA; }
|
||||
force-xinerama { return TOK_FORCE_XINERAMA; }
|
||||
fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
|
||||
fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; }
|
||||
workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; }
|
||||
force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; }
|
||||
ms { return TOK_TIME_MS; }
|
||||
workspace_bar { return TOKWORKSPACEBAR; }
|
||||
popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; }
|
||||
ignore { return TOK_IGNORE; }
|
||||
leave_fullscreen { return TOK_LEAVE_FULLSCREEN; }
|
||||
for_window {
|
||||
/* Example: for_window [class="urxvt"] border none
|
||||
*
|
||||
* First, we wait for the ']' that finishes a match (FOR_WINDOW_COND)
|
||||
* Then, we require a whitespace (EAT_WHITESPACE)
|
||||
* And the rest of the line is parsed as a string
|
||||
*/
|
||||
yy_push_state(WANT_STRING);
|
||||
yy_push_state(EAT_WHITESPACE);
|
||||
yy_push_state(FOR_WINDOW_COND);
|
||||
return TOK_FOR_WINDOW;
|
||||
}
|
||||
default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; }
|
||||
stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; }
|
||||
stacked { return TOK_STACKING; }
|
||||
tabbed { /* yylval.number = MODE_TABBED; */return TOK_TABBED; }
|
||||
stack-limit { return TOKSTACKLIMIT; }
|
||||
cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; }
|
||||
rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; }
|
||||
exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; }
|
||||
exec_always { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; }
|
||||
client.background { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; }
|
||||
client.focused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
|
||||
client.focused_inactive { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
|
||||
client.unfocused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
|
||||
client.urgent { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
|
||||
bar.focused { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
|
||||
bar.unfocused { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
|
||||
bar.urgent { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
|
||||
Mod1 { yylval.number = BIND_MOD1; return MODIFIER; }
|
||||
Mod2 { yylval.number = BIND_MOD2; return MODIFIER; }
|
||||
Mod3 { yylval.number = BIND_MOD3; return MODIFIER; }
|
||||
Mod4 { yylval.number = BIND_MOD4; return MODIFIER; }
|
||||
Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
|
||||
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
|
||||
control { return TOKCONTROL; }
|
||||
ctrl { return TOKCONTROL; }
|
||||
shift { return TOKSHIFT; }
|
||||
|
||||
class { yy_push_state(WANT_QSTRING); return TOK_CLASS; }
|
||||
instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; }
|
||||
window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; }
|
||||
id { yy_push_state(WANT_QSTRING); return TOK_ID; }
|
||||
con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; }
|
||||
con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; }
|
||||
title { yy_push_state(WANT_QSTRING); return TOK_TITLE; }
|
||||
urgent { yy_push_state(WANT_QSTRING); return TOK_URGENT; }
|
||||
|
||||
<*>{EOL} {
|
||||
FREE(context->line_copy);
|
||||
context->line_number++;
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
<BINDSYM_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
|
||||
<OUTPUT_COND>[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); }
|
||||
[ \t]+ { /* ignore whitespace */ ; }
|
||||
\"[^\"]+\" {
|
||||
/* if ASSIGN_COND then */
|
||||
if (yy_start_stack_ptr > 0)
|
||||
yy_pop_state();
|
||||
/* yylval will be the string, but without quotes */
|
||||
char *copy = sstrdup(yytext+1);
|
||||
copy[strlen(copy)-1] = '\0';
|
||||
yylval.string = copy;
|
||||
return QUOTEDSTRING;
|
||||
}
|
||||
<ASSIGN_COND>[^ \t\"\[]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; }
|
||||
<BINDSYM_COND>[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; }
|
||||
[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; }
|
||||
. { return (int)yytext[0]; }
|
||||
|
||||
<<EOF>> {
|
||||
while (yy_start_stack_ptr > 0)
|
||||
yy_pop_state();
|
||||
yyterminate();
|
||||
}
|
||||
|
||||
%%
|
1875
src/cfgparse.y
1875
src/cfgparse.y
File diff suppressed because it is too large
Load Diff
@ -55,23 +55,15 @@ static bool definitelyGreaterThan(float a, float b, float epsilon) {
|
||||
static Output *get_output_from_string(Output *current_output, const char *output_str) {
|
||||
Output *output;
|
||||
|
||||
if (strcasecmp(output_str, "left") == 0) {
|
||||
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
|
||||
if (!output)
|
||||
output = get_output_most(D_RIGHT, current_output);
|
||||
} else if (strcasecmp(output_str, "right") == 0) {
|
||||
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
|
||||
if (!output)
|
||||
output = get_output_most(D_LEFT, current_output);
|
||||
} else if (strcasecmp(output_str, "up") == 0) {
|
||||
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
|
||||
if (!output)
|
||||
output = get_output_most(D_DOWN, current_output);
|
||||
} else if (strcasecmp(output_str, "down") == 0) {
|
||||
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
|
||||
if (!output)
|
||||
output = get_output_most(D_UP, current_output);
|
||||
} else output = get_output_by_name(output_str);
|
||||
if (strcasecmp(output_str, "left") == 0)
|
||||
output = get_output_next_wrap(D_LEFT, current_output);
|
||||
else if (strcasecmp(output_str, "right") == 0)
|
||||
output = get_output_next_wrap(D_RIGHT, current_output);
|
||||
else if (strcasecmp(output_str, "up") == 0)
|
||||
output = get_output_next_wrap(D_UP, current_output);
|
||||
else if (strcasecmp(output_str, "down") == 0)
|
||||
output = get_output_next_wrap(D_DOWN, current_output);
|
||||
else output = get_output_by_name(output_str);
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -596,10 +588,14 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin
|
||||
return;
|
||||
|
||||
if (strcmp(direction, "up") == 0) {
|
||||
floating_con->rect.y -= px;
|
||||
floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
|
||||
} else if (strcmp(direction, "left") == 0) {
|
||||
floating_con->rect.x -= px;
|
||||
floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
|
||||
}
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
}
|
||||
|
||||
static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) {
|
||||
@ -1052,13 +1048,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
|
||||
|
||||
// TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
|
||||
if (strcasecmp(name, "up") == 0)
|
||||
output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
|
||||
output = get_output_next_wrap(D_UP, current_output);
|
||||
else if (strcasecmp(name, "down") == 0)
|
||||
output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
|
||||
output = get_output_next_wrap(D_DOWN, current_output);
|
||||
else if (strcasecmp(name, "left") == 0)
|
||||
output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
|
||||
output = get_output_next_wrap(D_LEFT, current_output);
|
||||
else if (strcasecmp(name, "right") == 0)
|
||||
output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
|
||||
output = get_output_next_wrap(D_RIGHT, current_output);
|
||||
else
|
||||
output = get_output_by_name(name);
|
||||
|
||||
@ -1410,6 +1406,7 @@ void cmd_focus(I3_CMD) {
|
||||
return;
|
||||
}
|
||||
|
||||
Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
|
||||
int count = 0;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH(current, &owindows, owindows) {
|
||||
@ -1426,6 +1423,16 @@ void cmd_focus(I3_CMD) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||
if (ws == __i3_scratch) {
|
||||
scratchpad_show(current->con);
|
||||
count++;
|
||||
/* While for the normal focus case we can change focus multiple
|
||||
* times and only a single window ends up focused, we could show
|
||||
* multiple scratchpad windows. So, rather break here. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the container is not on the current workspace,
|
||||
* workspace_show() will switch to a different workspace and (if
|
||||
* enabled) trigger a mouse pointer warp to the currently focused
|
||||
@ -1602,8 +1609,8 @@ void cmd_exit(I3_CMD) {
|
||||
*/
|
||||
void cmd_reload(I3_CMD) {
|
||||
LOG("reloading\n");
|
||||
kill_configerror_nagbar(false);
|
||||
kill_commanderror_nagbar(false);
|
||||
kill_nagbar(&config_error_nagbar_pid, false);
|
||||
kill_nagbar(&command_error_nagbar_pid, false);
|
||||
load_configuration(conn, NULL, true);
|
||||
x_set_i3_atoms();
|
||||
/* Send an IPC event just in case the ws names have changed */
|
||||
|
57
src/con.c
57
src/con.c
@ -762,14 +762,9 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||
con_focus(old_focus);
|
||||
}
|
||||
|
||||
/* 8: when moving to a visible workspace on a different output, we keep the
|
||||
* con focused. Otherwise, we leave the focus on the current workspace as we
|
||||
* don’t want to focus invisible workspaces */
|
||||
if (source_output != dest_output &&
|
||||
workspace_is_visible(workspace) &&
|
||||
!con_is_internal(workspace)) {
|
||||
DLOG("Moved to a different output, focusing target\n");
|
||||
} else {
|
||||
/* 8: when moving to another workspace, we leave the focus on the current
|
||||
* workspace. (see also #809) */
|
||||
|
||||
/* Descend focus stack in case focus_next is a workspace which can
|
||||
* occur if we move to the same workspace. Also show current workspace
|
||||
* to ensure it is focused. */
|
||||
@ -779,7 +774,6 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
||||
* Otherwise we would give focus to some window on different workspace. */
|
||||
if (source_ws == current_ws)
|
||||
con_focus(con_descend_focused(focus_next));
|
||||
}
|
||||
|
||||
/* If anything within the container is associated with a startup sequence,
|
||||
* delete it so child windows won't be created on the old workspace. */
|
||||
@ -1181,7 +1175,7 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
|
||||
con->current_border_width = border_width;
|
||||
bsr = con_border_style_rect(con);
|
||||
int deco_height =
|
||||
(con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
|
||||
(con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
||||
|
||||
con->rect.x -= bsr.x;
|
||||
con->rect.y -= bsr.y;
|
||||
@ -1536,6 +1530,45 @@ void con_update_parents_urgency(Con *con) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set urgency flag to the container, all the parent containers and the workspace.
|
||||
*
|
||||
*/
|
||||
void con_set_urgency(Con *con, bool urgent) {
|
||||
if (focused == con) {
|
||||
DLOG("Ignoring urgency flag for current client\n");
|
||||
con->window->urgent.tv_sec = 0;
|
||||
con->window->urgent.tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (con->urgency_timer == NULL) {
|
||||
con->urgent = urgent;
|
||||
} else
|
||||
DLOG("Discarding urgency WM_HINT because timer is running\n");
|
||||
|
||||
//CLIENT_LOG(con);
|
||||
if (con->window) {
|
||||
if (con->urgent) {
|
||||
gettimeofday(&con->window->urgent, NULL);
|
||||
} else {
|
||||
con->window->urgent.tv_sec = 0;
|
||||
con->window->urgent.tv_usec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
con_update_parents_urgency(con);
|
||||
|
||||
if (con->urgent == urgent)
|
||||
LOG("Urgency flag changed to %d\n", con->urgent);
|
||||
|
||||
Con *ws;
|
||||
/* Set the urgency flag on the workspace, if a workspace could be found
|
||||
* (for dock clients, that is not the case). */
|
||||
if ((ws = con_get_workspace(con)) != NULL)
|
||||
workspace_update_urgent_flag(ws);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a string representing the subtree under con.
|
||||
*
|
||||
@ -1573,6 +1606,10 @@ char *con_get_tree_representation(Con *con) {
|
||||
buf = sstrdup("T[");
|
||||
else if (con->layout == L_STACKED)
|
||||
buf = sstrdup("S[");
|
||||
else {
|
||||
ELOG("BUG: Code not updated to account for new layout type\n");
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/* 2) append representation of children */
|
||||
Con *child;
|
||||
|
@ -6,8 +6,8 @@
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* config.c: Configuration file (calling the parser (src/cfgparse.y) with the
|
||||
* correct path, switching key bindings mode).
|
||||
* config.c: Configuration file (calling the parser (src/config_parser.c) with
|
||||
* the correct path, switching key bindings mode).
|
||||
*
|
||||
*/
|
||||
#include "all.h"
|
||||
|
@ -392,8 +392,13 @@ CFGFUN(restart_state, const char *path) {
|
||||
}
|
||||
|
||||
CFGFUN(popup_during_fullscreen, const char *value) {
|
||||
config.popup_during_fullscreen =
|
||||
(strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN);
|
||||
if (strcmp(value, "ignore") == 0) {
|
||||
config.popup_during_fullscreen = PDF_IGNORE;
|
||||
} else if (strcmp(value, "leave_fullscreen") == 0) {
|
||||
config.popup_during_fullscreen = PDF_LEAVE_FULLSCREEN;
|
||||
} else {
|
||||
config.popup_during_fullscreen = PDF_SMART;
|
||||
}
|
||||
}
|
||||
|
||||
CFGFUN(color_single, const char *colorclass, const char *color) {
|
||||
@ -526,7 +531,10 @@ CFGFUN(bar_tray_output, const char *output) {
|
||||
CFGFUN(bar_color_single, const char *colorclass, const char *color) {
|
||||
if (strcmp(colorclass, "background") == 0)
|
||||
current_bar.colors.background = sstrdup(color);
|
||||
else current_bar.colors.statusline = sstrdup(color);
|
||||
else if (strcmp(colorclass, "separator") == 0)
|
||||
current_bar.colors.separator = sstrdup(color);
|
||||
else
|
||||
current_bar.colors.statusline = sstrdup(color);
|
||||
}
|
||||
|
||||
CFGFUN(bar_status_command, const char *command) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* config_parser.c: hand-written parser to parse configuration directives.
|
||||
*
|
||||
@ -31,6 +31,10 @@
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "all.h"
|
||||
|
||||
@ -38,6 +42,11 @@
|
||||
#define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
|
||||
#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
|
||||
|
||||
#ifndef TEST_PARSER
|
||||
pid_t config_error_nagbar_pid = -1;
|
||||
static struct context *context;
|
||||
#endif
|
||||
|
||||
/*******************************************************************************
|
||||
* The data structures used for parsing. Essentially the current state and a
|
||||
* list of tokens for that state.
|
||||
@ -649,4 +658,441 @@ int main(int argc, char *argv[]) {
|
||||
context.filename = "<stdin>";
|
||||
parse_config(argv[1], &context);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Goes through each line of buf (separated by \n) and checks for statements /
|
||||
* commands which only occur in i3 v4 configuration files. If it finds any, it
|
||||
* returns version 4, otherwise it returns version 3.
|
||||
*
|
||||
*/
|
||||
static int detect_version(char *buf) {
|
||||
char *walk = buf;
|
||||
char *line = buf;
|
||||
while (*walk != '\0') {
|
||||
if (*walk != '\n') {
|
||||
walk++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check for some v4-only statements */
|
||||
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
|
||||
strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
|
||||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
|
||||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
|
||||
printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* if this is a bind statement, we can check the command */
|
||||
if (strncasecmp(line, "bind", strlen("bind")) == 0) {
|
||||
char *bind = strchr(line, ' ');
|
||||
if (bind == NULL)
|
||||
goto next;
|
||||
while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
|
||||
bind++;
|
||||
if (*bind == '\0')
|
||||
goto next;
|
||||
if ((bind = strchr(bind, ' ')) == NULL)
|
||||
goto next;
|
||||
while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
|
||||
bind++;
|
||||
if (*bind == '\0')
|
||||
goto next;
|
||||
if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
|
||||
strncasecmp(bind, "floating", strlen("floating")) == 0 ||
|
||||
strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
|
||||
strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
|
||||
strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
|
||||
strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
|
||||
strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
|
||||
strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
|
||||
strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
|
||||
strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
|
||||
strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
|
||||
strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
|
||||
strncasecmp(bind, "bar", strlen("bar")) == 0) {
|
||||
printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
next:
|
||||
/* advance to the next line */
|
||||
walk++;
|
||||
line = walk;
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls i3-migrate-config-to-v4 to migrate a configuration file (input
|
||||
* buffer).
|
||||
*
|
||||
* Returns the converted config file or NULL if there was an error (for
|
||||
* example the script could not be found in $PATH or the i3 executable’s
|
||||
* directory).
|
||||
*
|
||||
*/
|
||||
static char *migrate_config(char *input, off_t size) {
|
||||
int writepipe[2];
|
||||
int readpipe[2];
|
||||
|
||||
if (pipe(writepipe) != 0 ||
|
||||
pipe(readpipe) != 0) {
|
||||
warn("migrate_config: Could not create pipes");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
warn("Could not fork()");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* child */
|
||||
if (pid == 0) {
|
||||
/* close writing end of writepipe, connect reading side to stdin */
|
||||
close(writepipe[1]);
|
||||
dup2(writepipe[0], 0);
|
||||
|
||||
/* close reading end of readpipe, connect writing side to stdout */
|
||||
close(readpipe[0]);
|
||||
dup2(readpipe[1], 1);
|
||||
|
||||
static char *argv[] = {
|
||||
NULL, /* will be replaced by the executable path */
|
||||
NULL
|
||||
};
|
||||
exec_i3_utility("i3-migrate-config-to-v4", argv);
|
||||
}
|
||||
|
||||
/* parent */
|
||||
|
||||
/* close reading end of the writepipe (connected to the script’s stdin) */
|
||||
close(writepipe[0]);
|
||||
|
||||
/* write the whole config file to the pipe, the script will read everything
|
||||
* immediately */
|
||||
int written = 0;
|
||||
int ret;
|
||||
while (written < size) {
|
||||
if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
|
||||
warn("Could not write to pipe");
|
||||
return NULL;
|
||||
}
|
||||
written += ret;
|
||||
}
|
||||
close(writepipe[1]);
|
||||
|
||||
/* close writing end of the readpipe (connected to the script’s stdout) */
|
||||
close(readpipe[1]);
|
||||
|
||||
/* read the script’s output */
|
||||
int conv_size = 65535;
|
||||
char *converted = malloc(conv_size);
|
||||
int read_bytes = 0;
|
||||
do {
|
||||
if (read_bytes == conv_size) {
|
||||
conv_size += 65535;
|
||||
converted = realloc(converted, conv_size);
|
||||
}
|
||||
ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
|
||||
if (ret == -1) {
|
||||
warn("Cannot read from pipe");
|
||||
FREE(converted);
|
||||
return NULL;
|
||||
}
|
||||
read_bytes += ret;
|
||||
} while (ret > 0);
|
||||
|
||||
/* get the returncode */
|
||||
int status;
|
||||
wait(&status);
|
||||
if (!WIFEXITED(status)) {
|
||||
fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int returncode = WEXITSTATUS(status);
|
||||
if (returncode != 0) {
|
||||
fprintf(stderr, "Migration process exit code was != 0\n");
|
||||
if (returncode == 2) {
|
||||
fprintf(stderr, "could not start the migration script\n");
|
||||
/* TODO: script was not found. tell the user to fix his system or create a v4 config */
|
||||
} else if (returncode == 1) {
|
||||
fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
|
||||
fprintf(stderr, "# i3 config file (v4)\n");
|
||||
/* TODO: nag the user with a message to include a hint for i3 in his config file */
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for duplicate key bindings (the same keycode or keysym is configured
|
||||
* more than once). If a duplicate binding is found, a message is printed to
|
||||
* stderr and the has_errors variable is set to true, which will start
|
||||
* i3-nagbar.
|
||||
*
|
||||
*/
|
||||
static void check_for_duplicate_bindings(struct context *context) {
|
||||
Binding *bind, *current;
|
||||
TAILQ_FOREACH(current, bindings, bindings) {
|
||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||
/* Abort when we reach the current keybinding, only check the
|
||||
* bindings before */
|
||||
if (bind == current)
|
||||
break;
|
||||
|
||||
/* Check if one is using keysym while the other is using bindsym.
|
||||
* If so, skip. */
|
||||
/* XXX: It should be checked at a later place (when translating the
|
||||
* keysym to keycodes) if there are any duplicates */
|
||||
if ((bind->symbol == NULL && current->symbol != NULL) ||
|
||||
(bind->symbol != NULL && current->symbol == NULL))
|
||||
continue;
|
||||
|
||||
/* If bind is NULL, current has to be NULL, too (see above).
|
||||
* If the keycodes differ, it can't be a duplicate. */
|
||||
if (bind->symbol != NULL &&
|
||||
strcasecmp(bind->symbol, current->symbol) != 0)
|
||||
continue;
|
||||
|
||||
/* Check if the keycodes or modifiers are different. If so, they
|
||||
* can't be duplicate */
|
||||
if (bind->keycode != current->keycode ||
|
||||
bind->mods != current->mods ||
|
||||
bind->release != current->release)
|
||||
continue;
|
||||
|
||||
context->has_errors = true;
|
||||
if (current->keycode != 0) {
|
||||
ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
|
||||
current->mods, current->keycode, current->command);
|
||||
} else {
|
||||
ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
|
||||
current->mods, current->symbol, current->command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the given file by first replacing the variables, then calling
|
||||
* parse_config and possibly launching i3-nagbar.
|
||||
*
|
||||
*/
|
||||
void parse_file(const char *f) {
|
||||
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
|
||||
int fd, ret, read_bytes = 0;
|
||||
struct stat stbuf;
|
||||
char *buf;
|
||||
FILE *fstr;
|
||||
char buffer[1026], key[512], value[512];
|
||||
|
||||
if ((fd = open(f, O_RDONLY)) == -1)
|
||||
die("Could not open configuration file: %s\n", strerror(errno));
|
||||
|
||||
if (fstat(fd, &stbuf) == -1)
|
||||
die("Could not fstat file: %s\n", strerror(errno));
|
||||
|
||||
buf = scalloc((stbuf.st_size + 1) * sizeof(char));
|
||||
while (read_bytes < stbuf.st_size) {
|
||||
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
|
||||
die("Could not read(): %s\n", strerror(errno));
|
||||
read_bytes += ret;
|
||||
}
|
||||
|
||||
if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
|
||||
die("Could not lseek: %s\n", strerror(errno));
|
||||
|
||||
if ((fstr = fdopen(fd, "r")) == NULL)
|
||||
die("Could not fdopen: %s\n", strerror(errno));
|
||||
|
||||
while (!feof(fstr)) {
|
||||
if (fgets(buffer, 1024, fstr) == NULL) {
|
||||
if (feof(fstr))
|
||||
break;
|
||||
die("Could not read configuration file\n");
|
||||
}
|
||||
|
||||
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
|
||||
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
|
||||
key[0] == '#' || strlen(key) < 3)
|
||||
continue;
|
||||
|
||||
if (strcasecmp(key, "set") == 0) {
|
||||
if (value[0] != '$') {
|
||||
ELOG("Malformed variable assignment, name has to start with $\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get key/value for this variable */
|
||||
char *v_key = value, *v_value;
|
||||
if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
|
||||
ELOG("Malformed variable assignment, need a value\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(v_value = strstr(value, " ")))
|
||||
v_value = strstr(value, "\t");
|
||||
|
||||
*(v_value++) = '\0';
|
||||
while (*v_value == '\t' || *v_value == ' ')
|
||||
v_value++;
|
||||
|
||||
struct Variable *new = scalloc(sizeof(struct Variable));
|
||||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_value);
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
DLOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fclose(fstr);
|
||||
|
||||
/* For every custom variable, see how often it occurs in the file and
|
||||
* how much extra bytes it requires when replaced. */
|
||||
struct Variable *current, *nearest;
|
||||
int extra_bytes = 0;
|
||||
/* We need to copy the buffer because we need to invalidate the
|
||||
* variables (otherwise we will count them twice, which is bad when
|
||||
* 'extra' is negative) */
|
||||
char *bufcopy = sstrdup(buf);
|
||||
SLIST_FOREACH(current, &variables, variables) {
|
||||
int extra = (strlen(current->value) - strlen(current->key));
|
||||
char *next;
|
||||
for (next = bufcopy;
|
||||
next < (bufcopy + stbuf.st_size) &&
|
||||
(next = strcasestr(next, current->key)) != NULL;
|
||||
next += strlen(current->key)) {
|
||||
*next = '_';
|
||||
extra_bytes += extra;
|
||||
}
|
||||
}
|
||||
FREE(bufcopy);
|
||||
|
||||
/* Then, allocate a new buffer and copy the file over to the new one,
|
||||
* but replace occurences of our variables */
|
||||
char *walk = buf, *destwalk;
|
||||
char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
|
||||
destwalk = new;
|
||||
while (walk < (buf + stbuf.st_size)) {
|
||||
/* Find the next variable */
|
||||
SLIST_FOREACH(current, &variables, variables)
|
||||
current->next_match = strcasestr(walk, current->key);
|
||||
nearest = NULL;
|
||||
int distance = stbuf.st_size;
|
||||
SLIST_FOREACH(current, &variables, variables) {
|
||||
if (current->next_match == NULL)
|
||||
continue;
|
||||
if ((current->next_match - walk) < distance) {
|
||||
distance = (current->next_match - walk);
|
||||
nearest = current;
|
||||
}
|
||||
}
|
||||
if (nearest == NULL) {
|
||||
/* If there are no more variables, we just copy the rest */
|
||||
strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
|
||||
destwalk += (buf + stbuf.st_size) - walk;
|
||||
*destwalk = '\0';
|
||||
break;
|
||||
} else {
|
||||
/* Copy until the next variable, then copy its value */
|
||||
strncpy(destwalk, walk, distance);
|
||||
strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
|
||||
walk += distance + strlen(nearest->key);
|
||||
destwalk += distance + strlen(nearest->value);
|
||||
}
|
||||
}
|
||||
|
||||
/* analyze the string to find out whether this is an old config file (3.x)
|
||||
* or a new config file (4.x). If it’s old, we run the converter script. */
|
||||
int version = detect_version(buf);
|
||||
if (version == 3) {
|
||||
/* We need to convert this v3 configuration */
|
||||
char *converted = migrate_config(new, stbuf.st_size);
|
||||
if (converted != NULL) {
|
||||
ELOG("\n");
|
||||
ELOG("****************************************************************\n");
|
||||
ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
|
||||
ELOG("\n");
|
||||
ELOG("Please convert your config file to v4. You can use this command:\n");
|
||||
ELOG(" mv %s %s.O\n", f, f);
|
||||
ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
|
||||
ELOG("****************************************************************\n");
|
||||
ELOG("\n");
|
||||
free(new);
|
||||
new = converted;
|
||||
} else {
|
||||
printf("\n");
|
||||
printf("**********************************************************************\n");
|
||||
printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
|
||||
printf("was not correctly installed on your system?\n");
|
||||
printf("**********************************************************************\n");
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
context = scalloc(sizeof(struct context));
|
||||
context->filename = f;
|
||||
|
||||
struct ConfigResult *config_output = parse_config(new, context);
|
||||
yajl_gen_free(config_output->json_gen);
|
||||
|
||||
check_for_duplicate_bindings(context);
|
||||
|
||||
if (context->has_errors || context->has_warnings) {
|
||||
ELOG("FYI: You are using i3 version " I3_VERSION "\n");
|
||||
if (version == 3)
|
||||
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
|
||||
|
||||
char *editaction,
|
||||
*pageraction;
|
||||
sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f);
|
||||
sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
|
||||
char *argv[] = {
|
||||
NULL, /* will be replaced by the executable path */
|
||||
"-f",
|
||||
config.font.pattern,
|
||||
"-t",
|
||||
(context->has_errors ? "error" : "warning"),
|
||||
"-m",
|
||||
(context->has_errors ?
|
||||
"You have an error in your i3 config file!" :
|
||||
"Your config is outdated. Please fix the warnings to make sure everything works."),
|
||||
"-b",
|
||||
"edit config",
|
||||
editaction,
|
||||
(errorfilename ? "-b" : NULL),
|
||||
(context->has_errors ? "show errors" : "show warnings"),
|
||||
pageraction,
|
||||
NULL
|
||||
};
|
||||
|
||||
start_nagbar(&config_error_nagbar_pid, argv);
|
||||
free(editaction);
|
||||
free(pageraction);
|
||||
}
|
||||
|
||||
FREE(context->line_copy);
|
||||
free(context);
|
||||
free(new);
|
||||
free(buf);
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
current = SLIST_FIRST(&variables);
|
||||
FREE(current->key);
|
||||
FREE(current->value);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
FREE(current);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -101,15 +101,18 @@ void display_running_version(void) {
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
|
||||
uint32_t reply_length;
|
||||
uint32_t reply_type;
|
||||
uint8_t *reply;
|
||||
int ret;
|
||||
if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_GET_VERSION,
|
||||
&reply_length, &reply)) != 0) {
|
||||
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
|
||||
if (ret == -1)
|
||||
err(EXIT_FAILURE, "IPC: read()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (reply_type != I3_IPC_MESSAGE_TYPE_GET_VERSION)
|
||||
errx(EXIT_FAILURE, "Got reply type %d, but expected %d (GET_VERSION)", reply_type, I3_IPC_MESSAGE_TYPE_GET_VERSION);
|
||||
|
||||
#if YAJL_MAJOR >= 2
|
||||
yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
|
||||
#else
|
||||
|
@ -164,5 +164,5 @@ void ewmh_setup_hints(void) {
|
||||
/* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
|
||||
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 16, supported_atoms);
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms);
|
||||
}
|
||||
|
@ -39,6 +39,35 @@ void floating_check_size(Con *floating_con) {
|
||||
const int floating_sane_min_height = 50;
|
||||
const int floating_sane_min_width = 75;
|
||||
Rect floating_sane_max_dimensions;
|
||||
Con *focused_con = con_descend_focused(floating_con);
|
||||
|
||||
/* obey size increments */
|
||||
if (focused_con->height_increment || focused_con->width_increment) {
|
||||
Rect border_rect = con_border_style_rect(focused_con);
|
||||
|
||||
/* We have to do the opposite calculations that render_con() do
|
||||
* to get the exact size we want. */
|
||||
border_rect.width = -border_rect.width;
|
||||
border_rect.width += 2 * focused_con->border_width;
|
||||
border_rect.height = -border_rect.height;
|
||||
border_rect.height += 2 * focused_con->border_width;
|
||||
if (con_border_style(focused_con) == BS_NORMAL)
|
||||
border_rect.height += render_deco_height();
|
||||
|
||||
if (focused_con->height_increment &&
|
||||
floating_con->rect.height >= focused_con->base_height + border_rect.height) {
|
||||
floating_con->rect.height -= focused_con->base_height + border_rect.height;
|
||||
floating_con->rect.height -= floating_con->rect.height % focused_con->height_increment;
|
||||
floating_con->rect.height += focused_con->base_height + border_rect.height;
|
||||
}
|
||||
|
||||
if (focused_con->width_increment &&
|
||||
floating_con->rect.width >= focused_con->base_width + border_rect.width) {
|
||||
floating_con->rect.width -= focused_con->base_width + border_rect.width;
|
||||
floating_con->rect.width -= floating_con->rect.width % focused_con->width_increment;
|
||||
floating_con->rect.width += focused_con->base_width + border_rect.width;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unless user requests otherwise (-1), ensure width/height do not exceed
|
||||
* configured maxima or, if unconfigured, limit to combined width of all
|
||||
@ -165,7 +194,7 @@ void floating_enable(Con *con, bool automatic) {
|
||||
free(name);
|
||||
|
||||
/* find the height for the decorations */
|
||||
int deco_height = config.font.height + 5;
|
||||
int deco_height = render_deco_height();
|
||||
|
||||
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
|
||||
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
|
||||
@ -251,7 +280,7 @@ void floating_enable(Con *con, bool automatic) {
|
||||
/* 5: Subtract the deco_height in order to make the floating window appear
|
||||
* at precisely the position it specified in its original geometry (which
|
||||
* is what applications might remember). */
|
||||
deco_height = (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
|
||||
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
||||
nc->rect.y -= deco_height;
|
||||
|
||||
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
|
||||
@ -409,6 +438,11 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
|
||||
|
||||
/* Drag the window */
|
||||
drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
|
||||
tree_render();
|
||||
}
|
||||
|
||||
@ -447,26 +481,27 @@ DRAGGING_CB(resize_window_callback) {
|
||||
dest_height = old_rect->height - (new_y - event->root_y);
|
||||
else dest_height = old_rect->height + (new_y - event->root_y);
|
||||
|
||||
/* Obey minimum window size */
|
||||
Rect minimum = con_minimum_size(con);
|
||||
dest_width = max(dest_width, minimum.width);
|
||||
dest_height = max(dest_height, minimum.height);
|
||||
|
||||
/* User wants to keep proportions, so we may have to adjust our values */
|
||||
if (params->proportional) {
|
||||
dest_width = max(dest_width, (int) (dest_height * ratio));
|
||||
dest_height = max(dest_height, (int) (dest_width / ratio));
|
||||
}
|
||||
|
||||
con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
|
||||
|
||||
/* Obey window size */
|
||||
floating_check_size(con);
|
||||
|
||||
/* If not the lower right corner is grabbed, we must also reposition
|
||||
* the client by exactly the amount we resized it */
|
||||
if (corner & BORDER_LEFT)
|
||||
dest_x = old_rect->x + (old_rect->width - dest_width);
|
||||
dest_x = old_rect->x + (old_rect->width - con->rect.width);
|
||||
|
||||
if (corner & BORDER_TOP)
|
||||
dest_y = old_rect->y + (old_rect->height - dest_height);
|
||||
dest_y = old_rect->y + (old_rect->height - con->rect.height);
|
||||
|
||||
con->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
|
||||
con->rect.x = dest_x;
|
||||
con->rect.y = dest_y;
|
||||
|
||||
/* TODO: don’t re-render the whole tree just because we change
|
||||
* coordinates of a floating window */
|
||||
@ -507,6 +542,10 @@ 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);
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -630,6 +669,11 @@ void floating_reposition(Con *con, Rect newrect) {
|
||||
con->rect = newrect;
|
||||
|
||||
floating_maybe_reassign_ws(con);
|
||||
|
||||
/* If this is a scratchpad window, don't auto center it from now on. */
|
||||
if (con->scratchpad_state == SCRATCHPAD_FRESH)
|
||||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
|
||||
tree_render();
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
|
||||
|
||||
if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
|
||||
/* find the height for the decorations */
|
||||
int deco_height = config.font.height + 5;
|
||||
int deco_height = con->deco_rect.height;
|
||||
/* we actually need to apply the size/position changes to the *parent*
|
||||
* container */
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
@ -619,10 +619,10 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
|
||||
LOG("ClientMessage for window 0x%08x\n", event->window);
|
||||
if (event->type == A__NET_WM_STATE) {
|
||||
if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
|
||||
DLOG("atom in clientmessage is %d, fullscreen is %d\n",
|
||||
event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
|
||||
DLOG("not about fullscreen atom\n");
|
||||
if (event->format != 32 ||
|
||||
(event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN &&
|
||||
event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION)) {
|
||||
DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -632,6 +632,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->data.data32[1] == A__NET_WM_STATE_FULLSCREEN) {
|
||||
/* Check if the fullscreen state should be toggled */
|
||||
if ((con->fullscreen_mode != CF_NONE &&
|
||||
(event->data.data32[0] == _NET_WM_STATE_REMOVE ||
|
||||
@ -642,6 +643,15 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
DLOG("toggling fullscreen\n");
|
||||
con_toggle_fullscreen(con, CF_OUTPUT);
|
||||
}
|
||||
} else if (event->data.data32[1] == A__NET_WM_STATE_DEMANDS_ATTENTION) {
|
||||
/* Check if the urgent flag must be set or not */
|
||||
if (event->data.data32[0] == _NET_WM_STATE_ADD)
|
||||
con_set_urgency(con, true);
|
||||
else if (event->data.data32[0] == _NET_WM_STATE_REMOVE)
|
||||
con_set_urgency(con, false);
|
||||
else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
|
||||
con_set_urgency(con, !con->urgent);
|
||||
}
|
||||
|
||||
tree_render();
|
||||
} else if (event->type == A__NET_ACTIVE_WINDOW) {
|
||||
@ -833,44 +843,12 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
|
||||
if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
|
||||
return false;
|
||||
|
||||
if (!con->urgent && focused == con) {
|
||||
DLOG("Ignoring urgency flag for current client\n");
|
||||
con->window->urgent.tv_sec = 0;
|
||||
con->window->urgent.tv_usec = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Update the flag on the client directly */
|
||||
bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
|
||||
|
||||
if (con->urgency_timer == NULL) {
|
||||
con->urgent = hint_urgent;
|
||||
} else
|
||||
DLOG("Discarding urgency WM_HINT because timer is running\n");
|
||||
|
||||
//CLIENT_LOG(con);
|
||||
if (con->window) {
|
||||
if (con->urgent) {
|
||||
gettimeofday(&con->window->urgent, NULL);
|
||||
} else {
|
||||
con->window->urgent.tv_sec = 0;
|
||||
con->window->urgent.tv_usec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
con_update_parents_urgency(con);
|
||||
|
||||
LOG("Urgency flag changed to %d\n", con->urgent);
|
||||
|
||||
Con *ws;
|
||||
/* Set the urgency flag on the workspace, if a workspace could be found
|
||||
* (for dock clients, that is not the case). */
|
||||
if ((ws = con_get_workspace(con)) != NULL)
|
||||
workspace_update_urgent_flag(ws);
|
||||
con_set_urgency(con, hint_urgent);
|
||||
|
||||
tree_render();
|
||||
|
||||
end:
|
||||
if (con->window)
|
||||
window_update_hints(con->window, reply);
|
||||
else free(reply);
|
||||
@ -1094,7 +1072,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||
|
||||
/* Client message are sent to the root window. The only interesting
|
||||
* client message for us is _NET_WM_STATE, we honour
|
||||
* _NET_WM_STATE_FULLSCREEN */
|
||||
* _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION */
|
||||
case XCB_CLIENT_MESSAGE:
|
||||
handle_client_message((xcb_client_message_event_t*)event);
|
||||
break;
|
||||
|
11
src/i3.mk
11
src/i3.mk
@ -2,7 +2,6 @@ ALL_TARGETS += i3
|
||||
INSTALL_TARGETS += install-i3
|
||||
CLEAN_TARGETS += clean-i3
|
||||
|
||||
i3_SOURCES_GENERATED = src/cfgparse.tab.c src/cfgparse.yy.c
|
||||
i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c))
|
||||
i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h)
|
||||
i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h))
|
||||
@ -37,14 +36,6 @@ src/%.o: src/%.c $(i3_HEADERS_DEP)
|
||||
echo "[i3] CC $<"
|
||||
$(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$<
|
||||
|
||||
src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP)
|
||||
echo "[i3] LEX $<"
|
||||
$(FLEX) -i -o $@ ${canonical_path}/$<
|
||||
|
||||
src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP)
|
||||
echo "[i3] YACC $<"
|
||||
$(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$<
|
||||
|
||||
# This target compiles the command parser twice:
|
||||
# Once with -DTEST_PARSER, creating a stand-alone executable used for tests,
|
||||
# and once as an object file for i3.
|
||||
@ -96,4 +87,4 @@ install-i3: i3
|
||||
|
||||
clean-i3:
|
||||
echo "[i3] Clean"
|
||||
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.*
|
||||
rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 test.config_parser test.commands_parser src/*.gcno src/cfgparse.* src/cmdparse.*
|
||||
|
61
src/ipc.c
61
src/ipc.c
@ -677,6 +677,7 @@ IPC_HANDLER(get_bar_config) {
|
||||
y(map_open);
|
||||
YSTR_IF_SET(background);
|
||||
YSTR_IF_SET(statusline);
|
||||
YSTR_IF_SET(separator);
|
||||
YSTR_IF_SET(focused_workspace_border);
|
||||
YSTR_IF_SET(focused_workspace_bg);
|
||||
YSTR_IF_SET(focused_workspace_text);
|
||||
@ -802,18 +803,16 @@ handler_t handlers[8] = {
|
||||
*
|
||||
*/
|
||||
static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
||||
char buf[2048];
|
||||
int n = read(w->fd, buf, sizeof(buf));
|
||||
uint32_t message_type;
|
||||
uint32_t message_length;
|
||||
uint8_t *message;
|
||||
|
||||
/* On error or an empty message, we close the connection */
|
||||
if (n <= 0) {
|
||||
#if 0
|
||||
/* FIXME: I get these when closing a client socket,
|
||||
* therefore we just treat them as an error. Is this
|
||||
* correct? */
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
int ret = ipc_recv_message(w->fd, &message_type, &message_length, &message);
|
||||
/* EOF or other error */
|
||||
if (ret < 0) {
|
||||
/* Was this a spurious read? See ev(3) */
|
||||
if (ret == -1 && errno == EAGAIN)
|
||||
return;
|
||||
#endif
|
||||
|
||||
/* If not, there was some kind of error. We don’t bother
|
||||
* and close the connection */
|
||||
@ -841,51 +840,11 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Terminate the message correctly */
|
||||
buf[n] = '\0';
|
||||
|
||||
/* Check if the message starts with the i3 IPC magic code */
|
||||
if (n < strlen(I3_IPC_MAGIC)) {
|
||||
DLOG("IPC: message too short, ignoring\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
|
||||
DLOG("IPC: message does not start with the IPC magic\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *message = (uint8_t*)buf;
|
||||
while (n > 0) {
|
||||
DLOG("IPC: n = %d\n", n);
|
||||
message += strlen(I3_IPC_MAGIC);
|
||||
n -= strlen(I3_IPC_MAGIC);
|
||||
|
||||
/* The next 32 bit after the magic are the message size */
|
||||
uint32_t message_size;
|
||||
memcpy(&message_size, (uint32_t*)message, sizeof(uint32_t));
|
||||
message += sizeof(uint32_t);
|
||||
n -= sizeof(uint32_t);
|
||||
|
||||
if (message_size > n) {
|
||||
DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* The last 32 bits of the header are the message type */
|
||||
uint32_t message_type;
|
||||
memcpy(&message_type, (uint32_t*)message, sizeof(uint32_t));
|
||||
message += sizeof(uint32_t);
|
||||
n -= sizeof(uint32_t);
|
||||
|
||||
if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
|
||||
DLOG("Unhandled message type: %d\n", message_type);
|
||||
else {
|
||||
handler_t h = handlers[message_type];
|
||||
h(w->fd, message, n, message_size, message_type);
|
||||
}
|
||||
n -= message_size;
|
||||
message += message_size;
|
||||
h(w->fd, message, 0, message_length, message_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
187
src/key_press.c
187
src/key_press.c
@ -4,7 +4,7 @@
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* key_press.c: key press handler
|
||||
*
|
||||
@ -19,169 +19,7 @@ static int current_nesting_level;
|
||||
static bool parse_error_key;
|
||||
static bool command_failed;
|
||||
|
||||
/* XXX: I don’t want to touch too much of the nagbar code at once, but we
|
||||
* should refactor this with src/cfgparse.y into a clean generic nagbar
|
||||
* interface. It might come in handy in other situations within i3, too. */
|
||||
static char *pager_script_path;
|
||||
static pid_t nagbar_pid = -1;
|
||||
|
||||
/*
|
||||
* Handler which will be called when we get a SIGCHLD for the nagbar, meaning
|
||||
* it exited (or could not be started, depending on the exit code).
|
||||
*
|
||||
*/
|
||||
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
|
||||
ev_child_stop(EV_A_ watcher);
|
||||
|
||||
if (unlink(pager_script_path) != 0)
|
||||
warn("Could not delete temporary i3-nagbar script %s", pager_script_path);
|
||||
|
||||
if (!WIFEXITED(watcher->rstatus)) {
|
||||
fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int exitcode = WEXITSTATUS(watcher->rstatus);
|
||||
printf("i3-nagbar process exited with status %d\n", exitcode);
|
||||
if (exitcode == 2) {
|
||||
fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
|
||||
}
|
||||
|
||||
nagbar_pid = -1;
|
||||
}
|
||||
|
||||
/* We need ev >= 4 for the following code. Since it is not *that* important (it
|
||||
* only makes sure that there are no i3-nagbar instances left behind) we still
|
||||
* support old systems with libev 3. */
|
||||
#if EV_VERSION_MAJOR >= 4
|
||||
/*
|
||||
* Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
|
||||
* SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
|
||||
*
|
||||
*/
|
||||
static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
|
||||
if (nagbar_pid != -1) {
|
||||
LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, nagbar_pid);
|
||||
kill(nagbar_pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Writes the given command as a shell script to path.
|
||||
* Returns true unless something went wrong.
|
||||
*
|
||||
*/
|
||||
static bool write_nagbar_script(const char *path, const char *command) {
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
|
||||
if (fd == -1) {
|
||||
warn("Could not create temporary script to store the nagbar command");
|
||||
return false;
|
||||
}
|
||||
write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n"));
|
||||
write(fd, command, strlen(command));
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Starts an i3-nagbar process which alerts the user that his configuration
|
||||
* file contains one or more errors. Also offers two buttons: One to launch an
|
||||
* $EDITOR on the config file and another one to launch a $PAGER on the error
|
||||
* logfile.
|
||||
*
|
||||
*/
|
||||
static void start_commanderror_nagbar(void) {
|
||||
if (nagbar_pid != -1) {
|
||||
DLOG("i3-nagbar for command error already running, not starting again.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Starting i3-nagbar due to command error\n");
|
||||
|
||||
/* We need to create a custom script containing our actual command
|
||||
* since not every terminal emulator which is contained in
|
||||
* i3-sensible-terminal supports -e with multiple arguments (and not
|
||||
* all of them support -e with one quoted argument either).
|
||||
*
|
||||
* NB: The paths need to be unique, that is, don’t assume users close
|
||||
* their nagbars at any point in time (and they still need to work).
|
||||
* */
|
||||
pager_script_path = get_process_filename("nagbar-cfgerror-pager");
|
||||
|
||||
nagbar_pid = fork();
|
||||
if (nagbar_pid == -1) {
|
||||
warn("Could not fork()");
|
||||
return;
|
||||
}
|
||||
|
||||
/* child */
|
||||
if (nagbar_pid == 0) {
|
||||
char *pager_command;
|
||||
sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename);
|
||||
if (!write_nagbar_script(pager_script_path, pager_command))
|
||||
return;
|
||||
|
||||
char *pageraction;
|
||||
sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path);
|
||||
char *argv[] = {
|
||||
NULL, /* will be replaced by the executable path */
|
||||
"-t",
|
||||
"error",
|
||||
"-m",
|
||||
"The configured command for this shortcut could not be run successfully.",
|
||||
"-b",
|
||||
"show errors",
|
||||
pageraction,
|
||||
NULL
|
||||
};
|
||||
exec_i3_utility("i3-nagbar", argv);
|
||||
}
|
||||
|
||||
/* parent */
|
||||
/* install a child watcher */
|
||||
ev_child *child = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child, &nagbar_exited, nagbar_pid, 0);
|
||||
ev_child_start(main_loop, child);
|
||||
|
||||
/* We need ev >= 4 for the following code. Since it is not *that* important (it
|
||||
* only makes sure that there are no i3-nagbar instances left behind) we still
|
||||
* support old systems with libev 3. */
|
||||
#if EV_VERSION_MAJOR >= 4
|
||||
/* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
|
||||
* still running) */
|
||||
ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
|
||||
ev_cleanup_init(cleanup, nagbar_cleanup);
|
||||
ev_cleanup_start(main_loop, cleanup);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Kills the commanderror i3-nagbar process, if any.
|
||||
*
|
||||
* Called when reloading/restarting, since the user probably fixed his wrong
|
||||
* keybindings.
|
||||
*
|
||||
* If wait_for_it is set (restarting), this function will waitpid(), otherwise,
|
||||
* ev is assumed to handle it (reloading).
|
||||
*
|
||||
*/
|
||||
void kill_commanderror_nagbar(bool wait_for_it) {
|
||||
if (nagbar_pid == -1)
|
||||
return;
|
||||
|
||||
if (kill(nagbar_pid, SIGTERM) == -1)
|
||||
warn("kill(configerror_nagbar) failed");
|
||||
|
||||
if (!wait_for_it)
|
||||
return;
|
||||
|
||||
/* When restarting, we don’t enter the ev main loop anymore and after the
|
||||
* exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
|
||||
* for us and we would end up with a <defunct> process. Therefore we
|
||||
* waitpid() here. */
|
||||
waitpid(nagbar_pid, NULL, 0);
|
||||
}
|
||||
pid_t command_error_nagbar_pid = -1;
|
||||
|
||||
static int json_boolean(void *ctx, int boolval) {
|
||||
DLOG("Got bool: %d, parse_error_key %d, nesting_level %d\n", boolval, parse_error_key, current_nesting_level);
|
||||
@ -302,8 +140,25 @@ void handle_key_press(xcb_key_press_event_t *event) {
|
||||
if (state != yajl_status_ok) {
|
||||
ELOG("Could not parse my own reply. That's weird. reply is %.*s\n", (int)length, reply);
|
||||
} else {
|
||||
if (command_failed)
|
||||
start_commanderror_nagbar();
|
||||
if (command_failed) {
|
||||
char *pageraction;
|
||||
sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
|
||||
char *argv[] = {
|
||||
NULL, /* will be replaced by the executable path */
|
||||
"-f",
|
||||
config.font.pattern,
|
||||
"-t",
|
||||
"error",
|
||||
"-m",
|
||||
"The configured command for this shortcut could not be run successfully.",
|
||||
"-b",
|
||||
"show errors",
|
||||
pageraction,
|
||||
NULL
|
||||
};
|
||||
start_nagbar(&command_error_nagbar_pid, argv);
|
||||
free(pageraction);
|
||||
}
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
|
24
src/main.c
24
src/main.c
@ -374,8 +374,7 @@ int main(int argc, char *argv[]) {
|
||||
fake_outputs = sstrdup(optarg);
|
||||
break;
|
||||
} else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) {
|
||||
LOG("FORCING OLD CONFIG PARSER!\n");
|
||||
force_old_config_parser = true;
|
||||
ELOG("You are passing --force-old-config-parser-v4.4-only, but that flag was removed by now.\n");
|
||||
break;
|
||||
}
|
||||
/* fall-through */
|
||||
@ -461,14 +460,16 @@ int main(int argc, char *argv[]) {
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
|
||||
uint32_t reply_length;
|
||||
uint32_t reply_type;
|
||||
uint8_t *reply;
|
||||
int ret;
|
||||
if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND,
|
||||
&reply_length, &reply)) != 0) {
|
||||
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
|
||||
if (ret == -1)
|
||||
err(EXIT_FAILURE, "IPC: read()");
|
||||
return 1;
|
||||
}
|
||||
if (reply_type != I3_IPC_MESSAGE_TYPE_COMMAND)
|
||||
errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_MESSAGE_TYPE_COMMAND);
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
return 0;
|
||||
}
|
||||
@ -542,17 +543,8 @@ int main(int argc, char *argv[]) {
|
||||
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
|
||||
}
|
||||
|
||||
uint32_t mask = XCB_CW_EVENT_MASK;
|
||||
uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video
|
||||
projector), the root window gets a
|
||||
ConfigureNotify */
|
||||
XCB_EVENT_MASK_POINTER_MOTION |
|
||||
XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||
XCB_EVENT_MASK_ENTER_WINDOW };
|
||||
xcb_void_cookie_t cookie;
|
||||
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
|
||||
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
|
||||
check_error(conn, cookie, "Another window manager seems to be running");
|
||||
|
||||
xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(conn, gcookie, NULL);
|
||||
@ -603,11 +595,11 @@ int main(int argc, char *argv[]) {
|
||||
int i1;
|
||||
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
|
||||
fprintf(stderr, "XKB not supported by X-server\n");
|
||||
return 1;
|
||||
xkb_supported = false;
|
||||
}
|
||||
/* end of ugliness */
|
||||
|
||||
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
|
||||
if (xkb_supported && !XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
|
||||
XkbMapNotifyMask | XkbStateNotifyMask,
|
||||
XkbMapNotifyMask | XkbStateNotifyMask)) {
|
||||
fprintf(stderr, "Could not set XKB event mask\n");
|
||||
|
55
src/manage.c
55
src/manage.c
@ -10,6 +10,9 @@
|
||||
*
|
||||
*/
|
||||
#include "all.h"
|
||||
#include "yajl_utils.h"
|
||||
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
@ -64,8 +67,41 @@ void restore_geometry(void) {
|
||||
con->rect.x, con->rect.y);
|
||||
}
|
||||
|
||||
/* Strictly speaking, this line doesn’t really belong here, but since we
|
||||
* are syncing, let’s un-register as a window manager first */
|
||||
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
|
||||
|
||||
/* Make sure our changes reach the X server, we restart/exit now */
|
||||
xcb_flush(conn);
|
||||
xcb_aux_sync(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following function sends a new window event, which consists
|
||||
* of fields "change" and "container", the latter containing a dump
|
||||
* of the window's container.
|
||||
*
|
||||
*/
|
||||
static void ipc_send_window_new_event(Con *con) {
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
yajl_gen gen = ygenalloc();
|
||||
|
||||
y(map_open);
|
||||
|
||||
ystr("change");
|
||||
ystr("new");
|
||||
|
||||
ystr("container");
|
||||
dump_node(gen, con, false);
|
||||
|
||||
y(map_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
ylength length;
|
||||
y(get_buf, &payload, &length);
|
||||
|
||||
ipc_send_event("window", I3_IPC_EVENT_WINDOW, (const char *)payload);
|
||||
y(free);
|
||||
setlocale(LC_NUMERIC, "");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -349,10 +385,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
con_toggle_fullscreen(fs, CF_OUTPUT);
|
||||
} else if (config.popup_during_fullscreen == PDF_SMART &&
|
||||
fs != NULL &&
|
||||
fs->window != NULL &&
|
||||
fs->window->id == cwindow->transient_for) {
|
||||
fs->window != NULL) {
|
||||
i3Window *transient_win = cwindow;
|
||||
while (transient_win != NULL &&
|
||||
transient_win->transient_for != XCB_NONE) {
|
||||
if (transient_win->transient_for == fs->window->id) {
|
||||
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
|
||||
con_focus(nc);
|
||||
break;
|
||||
}
|
||||
Con *next_transient = con_by_window_id(transient_win->transient_for);
|
||||
if (next_transient == NULL)
|
||||
break;
|
||||
transient_win = next_transient->window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,6 +470,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
}
|
||||
tree_render();
|
||||
|
||||
/* Send an event about window creation */
|
||||
ipc_send_window_new_event(nc);
|
||||
|
||||
geom_out:
|
||||
free(geom);
|
||||
out:
|
||||
|
@ -4,7 +4,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)
|
||||
*
|
||||
* output.c: Output (monitor) related functions.
|
||||
*
|
||||
@ -22,6 +22,5 @@ Con *output_get_content(Con *output) {
|
||||
if (child->type == CT_CON)
|
||||
return child;
|
||||
|
||||
ELOG("output_get_content() called on non-output %p\n", output);
|
||||
assert(false);
|
||||
return NULL;
|
||||
}
|
||||
|
34
src/randr.c
34
src/randr.c
@ -93,15 +93,30 @@ Output *get_output_containing(int x, int y) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the output which is the last one in the given direction, for example
|
||||
* the output on the most bottom when direction == D_DOWN, the output most
|
||||
* right when direction == D_RIGHT and so on.
|
||||
* Like get_output_next with close_far == CLOSEST_OUTPUT, but wraps.
|
||||
*
|
||||
* This function always returns a output.
|
||||
* For example if get_output_next(D_DOWN, x, FARTHEST_OUTPUT) = NULL, then
|
||||
* get_output_next_wrap(D_DOWN, x) will return the topmost output.
|
||||
*
|
||||
* This function always returns a output: if no active outputs can be found,
|
||||
* current itself is returned.
|
||||
*
|
||||
*/
|
||||
Output *get_output_most(direction_t direction, Output *current) {
|
||||
Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
|
||||
Output *get_output_next_wrap(direction_t direction, Output *current) {
|
||||
Output *best = get_output_next(direction, current, CLOSEST_OUTPUT);
|
||||
/* If no output can be found, wrap */
|
||||
if (!best) {
|
||||
direction_t opposite;
|
||||
if (direction == D_RIGHT)
|
||||
opposite = D_LEFT;
|
||||
else if (direction == D_LEFT)
|
||||
opposite = D_RIGHT;
|
||||
else if (direction == D_DOWN)
|
||||
opposite = D_UP;
|
||||
else
|
||||
opposite = D_DOWN;
|
||||
best = get_output_next(opposite, current, FARTHEST_OUTPUT);
|
||||
}
|
||||
if (!best)
|
||||
best = current;
|
||||
DLOG("current = %s, best = %s\n", current->name, best->name);
|
||||
@ -111,6 +126,13 @@ Output *get_output_most(direction_t direction, Output *current) {
|
||||
/*
|
||||
* Gets the output which is the next one in the given direction.
|
||||
*
|
||||
* If close_far == CLOSEST_OUTPUT, then the output next to the current one will
|
||||
* selected. If close_far == FARTHEST_OUTPUT, the output which is the last one
|
||||
* in the given direction will be selected.
|
||||
*
|
||||
* NULL will be returned when no active outputs are present in the direction
|
||||
* specified (note that “current” counts as such an output).
|
||||
*
|
||||
*/
|
||||
Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
|
||||
Rect *cur = &(current->rect),
|
||||
|
57
src/render.c
57
src/render.c
@ -16,6 +16,16 @@
|
||||
* container (for debugging purposes) */
|
||||
static bool show_debug_borders = false;
|
||||
|
||||
/*
|
||||
* Returns the height for the decorations
|
||||
*/
|
||||
int render_deco_height(void) {
|
||||
int deco_height = config.font.height + 4;
|
||||
if (config.font.height & 0x01)
|
||||
++deco_height;
|
||||
return deco_height;
|
||||
}
|
||||
|
||||
/*
|
||||
* Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs
|
||||
* get the height of their content and the remaining CT_CON gets the rest.
|
||||
@ -44,12 +54,19 @@ static void render_l_output(Con *con) {
|
||||
}
|
||||
}
|
||||
|
||||
assert(content != NULL);
|
||||
if (content == NULL) {
|
||||
DLOG("Skipping this output because it is currently being destroyed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to find out if there is a fullscreen con on the current workspace
|
||||
* and take the short-cut to render it directly (the user does not want to
|
||||
* see the dockareas in that case) */
|
||||
Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
|
||||
if (!ws) {
|
||||
DLOG("Skipping this output because it is currently being destroyed.\n");
|
||||
return;
|
||||
}
|
||||
Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
|
||||
if (fullscreen) {
|
||||
fullscreen->rect = con->rect;
|
||||
@ -196,12 +213,11 @@ void render_con(Con *con, bool render_fullscreen) {
|
||||
}
|
||||
|
||||
/* find the height for the decorations */
|
||||
int deco_height = config.font.height + 4;
|
||||
if (config.font.height & 0x01)
|
||||
++deco_height;
|
||||
int deco_height = render_deco_height();
|
||||
|
||||
/* precalculate the sizes to be able to correct rounding errors */
|
||||
int sizes[children];
|
||||
memset(sizes, 0, children*sizeof(int));
|
||||
if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && children > 0) {
|
||||
assert(!TAILQ_EMPTY(&con->nodes_head));
|
||||
Con *child;
|
||||
@ -244,6 +260,10 @@ void render_con(Con *con, bool render_fullscreen) {
|
||||
continue;
|
||||
/* Get the active workspace of that output */
|
||||
Con *content = output_get_content(output);
|
||||
if (!content || TAILQ_EMPTY(&(content->focus_head))) {
|
||||
DLOG("Skipping this output because it is currently being destroyed.\n");
|
||||
continue;
|
||||
}
|
||||
Con *workspace = TAILQ_FIRST(&(content->focus_head));
|
||||
Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
|
||||
Con *child;
|
||||
@ -251,18 +271,28 @@ void render_con(Con *con, bool render_fullscreen) {
|
||||
/* Don’t render floating windows when there is a fullscreen window
|
||||
* on that workspace. Necessary to make floating fullscreen work
|
||||
* correctly (ticket #564). */
|
||||
if (fullscreen != NULL) {
|
||||
if (fullscreen != NULL && fullscreen->window != NULL) {
|
||||
Con *floating_child = con_descend_focused(child);
|
||||
Con *transient_con = floating_child;
|
||||
bool is_transient_for = false;
|
||||
/* Exception to the above rule: smart
|
||||
* popup_during_fullscreen handling (popups belonging to
|
||||
* the fullscreen app will be rendered). */
|
||||
if (floating_child->window == NULL ||
|
||||
fullscreen->window == NULL ||
|
||||
floating_child->window->transient_for != fullscreen->window->id)
|
||||
while (transient_con != NULL &&
|
||||
transient_con->window != NULL &&
|
||||
transient_con->window->transient_for != XCB_NONE) {
|
||||
if (transient_con->window->transient_for == fullscreen->window->id) {
|
||||
is_transient_for = true;
|
||||
break;
|
||||
}
|
||||
transient_con = con_by_window_id(transient_con->window->transient_for);
|
||||
}
|
||||
|
||||
if (!is_transient_for)
|
||||
continue;
|
||||
else {
|
||||
DLOG("Rendering floating child even though in fullscreen mode: "
|
||||
"floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n",
|
||||
"floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
|
||||
floating_child->window->transient_for, fullscreen->window->id);
|
||||
}
|
||||
}
|
||||
@ -297,7 +327,8 @@ void render_con(Con *con, bool render_fullscreen) {
|
||||
}
|
||||
|
||||
/* first we have the decoration, if this is a leaf node */
|
||||
if (con_is_leaf(child) && child->border_style == BS_NORMAL) {
|
||||
if (con_is_leaf(child)) {
|
||||
if (child->border_style == BS_NORMAL) {
|
||||
/* TODO: make a function for relative coords? */
|
||||
child->deco_rect.x = child->rect.x - con->rect.x;
|
||||
child->deco_rect.y = child->rect.y - con->rect.y;
|
||||
@ -307,6 +338,12 @@ void render_con(Con *con, bool render_fullscreen) {
|
||||
|
||||
child->deco_rect.width = child->rect.width;
|
||||
child->deco_rect.height = deco_height;
|
||||
} else {
|
||||
child->deco_rect.x = 0;
|
||||
child->deco_rect.y = 0;
|
||||
child->deco_rect.width = 0;
|
||||
child->deco_rect.height = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,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)
|
||||
*
|
||||
* scratchpad.c: Moving windows to the scratchpad and making them visible again.
|
||||
*
|
||||
@ -53,7 +53,6 @@ void scratchpad_move(Con *con) {
|
||||
|
||||
/* 2: Send the window to the __i3_scratch workspace, mainting its
|
||||
* coordinates and not warping the pointer. */
|
||||
Con *focus_next = con_next_focused(con);
|
||||
con_move_to_workspace(con, __i3_scratch, true, true);
|
||||
|
||||
/* 3: If this is the first time this window is used as a scratchpad, we set
|
||||
@ -63,11 +62,6 @@ void scratchpad_move(Con *con) {
|
||||
DLOG("This window was never used as a scratchpad before.\n");
|
||||
con->scratchpad_state = SCRATCHPAD_FRESH;
|
||||
}
|
||||
|
||||
/* 4: Fix focus. Normally, when moving a window to a different output, the
|
||||
* destination output gets focused. In this case, we don’t want that. */
|
||||
if (con_get_workspace(focus_next) == con_get_workspace(focused))
|
||||
con_focus(focus_next);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -94,6 +88,41 @@ void scratchpad_show(Con *con) {
|
||||
con_toggle_fullscreen(focused, CF_OUTPUT);
|
||||
}
|
||||
|
||||
/* If this was 'scratchpad show' without criteria, we check if there is a
|
||||
* unfocused scratchpad on the current workspace and focus it */
|
||||
Con *walk_con;
|
||||
Con *focused_ws = con_get_workspace(focused);
|
||||
TAILQ_FOREACH(walk_con, &(focused_ws->floating_head), floating_windows) {
|
||||
if ((floating = con_inside_floating(walk_con)) &&
|
||||
floating->scratchpad_state != SCRATCHPAD_NONE &&
|
||||
floating != con_inside_floating(focused)) {
|
||||
DLOG("Found an unfocused scratchpad window on this workspace\n");
|
||||
DLOG("Focusing it: %p\n", walk_con);
|
||||
/* use con_descend_tiling_focused to get the last focused
|
||||
* window inside this scratch container in order to
|
||||
* keep the focus the same within this container */
|
||||
con_focus(con_descend_tiling_focused(walk_con));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this was 'scratchpad show' without criteria, we check if there is a
|
||||
* visible scratchpad window on another workspace. In this case we move it
|
||||
* to the current workspace. */
|
||||
focused_ws = con_get_workspace(focused);
|
||||
TAILQ_FOREACH(walk_con, &all_cons, all_cons) {
|
||||
Con *walk_ws = con_get_workspace(walk_con);
|
||||
if (walk_ws &&
|
||||
!con_is_internal(walk_ws) && focused_ws != walk_ws &&
|
||||
(floating = con_inside_floating(walk_con)) &&
|
||||
floating->scratchpad_state != SCRATCHPAD_NONE) {
|
||||
DLOG("Found a visible scratchpad window on another workspace,\n");
|
||||
DLOG("moving it to this workspace: con = %p\n", walk_con);
|
||||
con_move_to_workspace(walk_con, focused_ws, true, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this was 'scratchpad show' without criteria, we check if the
|
||||
* currently focused window is a scratchpad window and should be hidden
|
||||
* again. */
|
||||
@ -144,11 +173,11 @@ void scratchpad_show(Con *con) {
|
||||
Con *output = con_get_output(con);
|
||||
con->rect.width = output->rect.width * 0.5;
|
||||
con->rect.height = output->rect.height * 0.75;
|
||||
floating_check_size(con);
|
||||
con->rect.x = output->rect.x +
|
||||
((output->rect.width / 2.0) - (con->rect.width / 2.0));
|
||||
con->rect.y = output->rect.y +
|
||||
((output->rect.height / 2.0) - (con->rect.height / 2.0));
|
||||
con->scratchpad_state = SCRATCHPAD_CHANGED;
|
||||
}
|
||||
|
||||
/* Activate active workspace if window is from another workspace to ensure
|
||||
|
71
src/tree.c
71
src/tree.c
@ -200,6 +200,13 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
|
||||
was_mapped = _is_con_mapped(con);
|
||||
}
|
||||
|
||||
/* remove the urgency hint of the workspace (if set) */
|
||||
if (con->urgent) {
|
||||
con->urgent = false;
|
||||
con_update_parents_urgency(con);
|
||||
workspace_update_urgent_flag(con_get_workspace(con));
|
||||
}
|
||||
|
||||
/* Get the container which is next focused */
|
||||
Con *next = con_next_focused(con);
|
||||
DLOG("next = %p, focused = %p\n", next, focused);
|
||||
@ -251,15 +258,28 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
|
||||
free(con->window);
|
||||
}
|
||||
|
||||
/* kill the X11 part of this container */
|
||||
x_con_kill(con);
|
||||
Con *ws = con_get_workspace(con);
|
||||
|
||||
/* Figure out which container to focus next before detaching 'con'. */
|
||||
if (con_is_floating(con)) {
|
||||
if (con == focused) {
|
||||
DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
|
||||
next = con_next_focused(parent);
|
||||
|
||||
dont_kill_parent = true;
|
||||
DLOG("Alright, focusing %p\n", next);
|
||||
} else {
|
||||
next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Detach the container so that it will not be rendered anymore. */
|
||||
con_detach(con);
|
||||
|
||||
/* disable urgency timer, if needed */
|
||||
if (con->urgency_timer != NULL) {
|
||||
DLOG("Removing urgency timer of con %p\n", con);
|
||||
workspace_update_urgent_flag(con_get_workspace(con));
|
||||
workspace_update_urgent_flag(ws);
|
||||
ev_timer_stop(main_loop, con->urgency_timer);
|
||||
FREE(con->urgency_timer);
|
||||
}
|
||||
@ -270,21 +290,24 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
|
||||
con_fix_percent(parent);
|
||||
}
|
||||
|
||||
/* Render the tree so that the surrounding containers take up the space
|
||||
* which 'con' does no longer occupy. If we don’t render here, there will
|
||||
* be a gap in our containers and that could trigger an EnterNotify for an
|
||||
* underlying container, see ticket #660.
|
||||
*
|
||||
* Rendering has to be avoided when dont_kill_parent is set (when
|
||||
* tree_close calls itself recursively) because the tree is in a
|
||||
* non-renderable state during that time. */
|
||||
if (!dont_kill_parent)
|
||||
tree_render();
|
||||
|
||||
/* kill the X11 part of this container */
|
||||
x_con_kill(con);
|
||||
|
||||
if (con_is_floating(con)) {
|
||||
Con *ws = con_get_workspace(con);
|
||||
DLOG("Container was floating, killing floating container\n");
|
||||
tree_close(parent, DONT_KILL_WINDOW, false, (con == focused));
|
||||
DLOG("parent container killed\n");
|
||||
if (con == focused) {
|
||||
DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws);
|
||||
/* go down the focus stack as far as possible */
|
||||
next = con_descend_focused(ws);
|
||||
|
||||
dont_kill_parent = true;
|
||||
DLOG("Alright, focusing %p\n", next);
|
||||
} else {
|
||||
next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
free(con->name);
|
||||
@ -350,17 +373,23 @@ void tree_close_con(kill_window_t kill_window) {
|
||||
*
|
||||
*/
|
||||
void tree_split(Con *con, orientation_t orientation) {
|
||||
/* for a workspace, we just need to change orientation */
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
DLOG("Workspace, simply changing orientation to %d\n", orientation);
|
||||
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
||||
return;
|
||||
}
|
||||
else if (con->type == CT_FLOATING_CON) {
|
||||
if (con->type == CT_FLOATING_CON) {
|
||||
DLOG("Floating containers can't be split.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
if (con_num_children(con) < 2) {
|
||||
DLOG("Just changing orientation of workspace\n");
|
||||
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
||||
return;
|
||||
} else {
|
||||
/* if there is more than one container on the workspace
|
||||
* move them into a new container and handle this instead */
|
||||
con = workspace_encapsulate(con);
|
||||
}
|
||||
}
|
||||
|
||||
Con *parent = con->parent;
|
||||
|
||||
/* Force re-rendering to make the indicator border visible. */
|
||||
|
142
src/util.c
142
src/util.c
@ -183,44 +183,6 @@ static char **append_argument(char **original, char *argument) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the name of a temporary file with the specified prefix.
|
||||
*
|
||||
*/
|
||||
char *get_process_filename(const char *prefix) {
|
||||
/* dir stores the directory path for this and all subsequent calls so that
|
||||
* we only create a temporary directory once per i3 instance. */
|
||||
static char *dir = NULL;
|
||||
if (dir == NULL) {
|
||||
/* Check if XDG_RUNTIME_DIR is set. If so, we use XDG_RUNTIME_DIR/i3 */
|
||||
if ((dir = getenv("XDG_RUNTIME_DIR"))) {
|
||||
char *tmp;
|
||||
sasprintf(&tmp, "%s/i3", dir);
|
||||
dir = tmp;
|
||||
if (!path_exists(dir)) {
|
||||
if (mkdir(dir, 0700) == -1) {
|
||||
perror("mkdir()");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* If not, we create a (secure) temp directory using the template
|
||||
* /tmp/i3-<user>.XXXXXX */
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *username = pw ? pw->pw_name : "unknown";
|
||||
sasprintf(&dir, "/tmp/i3-%s.XXXXXX", username);
|
||||
/* mkdtemp modifies dir */
|
||||
if (mkdtemp(dir) == NULL) {
|
||||
perror("mkdtemp()");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
char *filename;
|
||||
sasprintf(&filename, "%s/%s.%d", dir, prefix, getpid());
|
||||
return filename;
|
||||
}
|
||||
|
||||
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
|
||||
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
|
||||
|
||||
@ -304,8 +266,8 @@ char *store_restart_layout(void) {
|
||||
void i3_restart(bool forget_layout) {
|
||||
char *restart_filename = forget_layout ? NULL : store_restart_layout();
|
||||
|
||||
kill_configerror_nagbar(true);
|
||||
kill_commanderror_nagbar(true);
|
||||
kill_nagbar(&config_error_nagbar_pid, true);
|
||||
kill_nagbar(&command_error_nagbar_pid, true);
|
||||
|
||||
restore_geometry();
|
||||
|
||||
@ -382,3 +344,103 @@ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Handler which will be called when we get a SIGCHLD for the nagbar, meaning
|
||||
* it exited (or could not be started, depending on the exit code).
|
||||
*
|
||||
*/
|
||||
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
|
||||
ev_child_stop(EV_A_ watcher);
|
||||
|
||||
if (!WIFEXITED(watcher->rstatus)) {
|
||||
ELOG("ERROR: i3-nagbar did not exit normally.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int exitcode = WEXITSTATUS(watcher->rstatus);
|
||||
DLOG("i3-nagbar process exited with status %d\n", exitcode);
|
||||
if (exitcode == 2) {
|
||||
ELOG("ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
|
||||
}
|
||||
|
||||
*((pid_t*)watcher->data) = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
|
||||
* SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
|
||||
*
|
||||
*/
|
||||
static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
|
||||
pid_t *nagbar_pid = (pid_t*)watcher->data;
|
||||
if (*nagbar_pid != -1) {
|
||||
LOG("Sending SIGKILL (%d) to i3-nagbar with PID %d\n", SIGKILL, *nagbar_pid);
|
||||
kill(*nagbar_pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Starts an i3-nagbar instance with the given parameters. Takes care of
|
||||
* handling SIGCHLD and killing i3-nagbar when i3 exits.
|
||||
*
|
||||
* The resulting PID will be stored in *nagbar_pid and can be used with
|
||||
* kill_nagbar() to kill the bar later on.
|
||||
*
|
||||
*/
|
||||
void start_nagbar(pid_t *nagbar_pid, char *argv[]) {
|
||||
if (*nagbar_pid != -1) {
|
||||
DLOG("i3-nagbar already running (PID %d), not starting again.\n", *nagbar_pid);
|
||||
return;
|
||||
}
|
||||
|
||||
*nagbar_pid = fork();
|
||||
if (*nagbar_pid == -1) {
|
||||
warn("Could not fork()");
|
||||
return;
|
||||
}
|
||||
|
||||
/* child */
|
||||
if (*nagbar_pid == 0)
|
||||
exec_i3_utility("i3-nagbar", argv);
|
||||
|
||||
DLOG("Starting i3-nagbar with PID %d\n", *nagbar_pid);
|
||||
|
||||
/* parent */
|
||||
/* install a child watcher */
|
||||
ev_child *child = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child, &nagbar_exited, *nagbar_pid, 0);
|
||||
child->data = nagbar_pid;
|
||||
ev_child_start(main_loop, child);
|
||||
|
||||
/* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
|
||||
* still running) */
|
||||
ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
|
||||
ev_cleanup_init(cleanup, nagbar_cleanup);
|
||||
cleanup->data = nagbar_pid;
|
||||
ev_cleanup_start(main_loop, cleanup);
|
||||
}
|
||||
|
||||
/*
|
||||
* Kills the i3-nagbar process, if *nagbar_pid != -1.
|
||||
*
|
||||
* If wait_for_it is set (restarting i3), this function will waitpid(),
|
||||
* otherwise, ev is assumed to handle it (reloading).
|
||||
*
|
||||
*/
|
||||
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
|
||||
if (*nagbar_pid == -1)
|
||||
return;
|
||||
|
||||
if (kill(*nagbar_pid, SIGTERM) == -1)
|
||||
warn("kill(configerror_nagbar) failed");
|
||||
|
||||
if (!wait_for_it)
|
||||
return;
|
||||
|
||||
/* When restarting, we don’t enter the ev main loop anymore and after the
|
||||
* exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
|
||||
* for us and we would end up with a <defunct> process. Therefore we
|
||||
* waitpid() here. */
|
||||
waitpid(*nagbar_pid, NULL, 0);
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ static void _workspace_show(Con *workspace) {
|
||||
* the corresponding workspace is cleaned up.
|
||||
* NOTE: Internal cons such as __i3_scratch (when a scratchpad window is
|
||||
* focused) are skipped, see bug #868. */
|
||||
if (current && !(current->name[0] == '_' && current->name[1] == '_')) {
|
||||
if (current && !con_is_internal(current)) {
|
||||
FREE(previous_workspace_name);
|
||||
if (current) {
|
||||
previous_workspace_name = sstrdup(current->name);
|
||||
|
24
src/x.c
24
src/x.c
@ -465,7 +465,7 @@ void x_draw_decoration(Con *con) {
|
||||
xcb_rectangle_t drect = { con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height };
|
||||
xcb_poly_fill_rectangle(conn, parent->pixmap, parent->pm_gc, 1, &drect);
|
||||
|
||||
/* 5: draw two unconnected lines in border color */
|
||||
/* 5: draw two unconnected horizontal lines in border color */
|
||||
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
|
||||
Rect *dr = &(con->deco_rect);
|
||||
int deco_diff_l = 2;
|
||||
@ -539,19 +539,21 @@ after_title:
|
||||
* the right border again after rendering the text (and the unconnected
|
||||
* lines in border color). */
|
||||
|
||||
/* Draw a separator line after every tab (except the last one), so that
|
||||
* tabs can be easily distinguished. */
|
||||
if (parent->layout == L_TABBED && TAILQ_NEXT(con, nodes) != NULL) {
|
||||
/* Draw a 1px separator line before and after every tab, so that tabs can
|
||||
* be easily distinguished. */
|
||||
if (parent->layout == L_TABBED) {
|
||||
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
|
||||
} else {
|
||||
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->background });
|
||||
}
|
||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 4,
|
||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, parent->pixmap, parent->pm_gc, 6,
|
||||
(xcb_point_t[]){
|
||||
{ dr->x + dr->width, dr->y },
|
||||
{ dr->x + dr->width, dr->y + dr->height },
|
||||
{ dr->x + dr->width - 1, dr->y },
|
||||
{ dr->x + dr->width - 1, dr->y + dr->height },
|
||||
{ dr->x + dr->width - 2, dr->y },
|
||||
{ dr->x + dr->width - 2, dr->y + dr->height }
|
||||
{ dr->x, dr->y + dr->height },
|
||||
{ dr->x, dr->y },
|
||||
});
|
||||
|
||||
xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
|
||||
@ -918,8 +920,12 @@ void x_push_changes(Con *con) {
|
||||
|
||||
Output *current = get_output_containing(pointerreply->root_x, pointerreply->root_y);
|
||||
Output *target = get_output_containing(mid_x, mid_y);
|
||||
if (current != target)
|
||||
if (current != target) {
|
||||
/* Ignore MotionNotify events generated by warping */
|
||||
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT });
|
||||
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
|
||||
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ ROOT_EVENT_MASK });
|
||||
}
|
||||
}
|
||||
warp_to = NULL;
|
||||
}
|
||||
@ -955,7 +961,7 @@ void x_push_changes(Con *con) {
|
||||
}
|
||||
|
||||
if (set_focus) {
|
||||
DLOG("Updating focus (focused: %p / %s)\n", focused, focused->name);
|
||||
DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus);
|
||||
/* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
|
||||
* no focus change events for our own focus changes. We only want
|
||||
* these generated by the clients. */
|
||||
|
10
testcases/.gitignore
vendored
Normal file
10
testcases/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
testsuite-*
|
||||
latest
|
||||
Makefile
|
||||
Makefile.old
|
||||
.last_run_timings.json
|
||||
_Inline
|
||||
inc
|
||||
META.yml
|
||||
i3-cfg-for-*
|
||||
-
|
@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
# © 2010-2011 Michael Stapelberg and contributors
|
||||
# © 2010-2012 Michael Stapelberg and contributors
|
||||
package complete_run;
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
use utf8;
|
||||
# the following are modules which ship with Perl (>= 5.10):
|
||||
use Pod::Usage;
|
||||
use Cwd qw(abs_path);
|
||||
@ -29,6 +30,9 @@ use AnyEvent::I3 qw(:all);
|
||||
use X11::XCB::Connection;
|
||||
use JSON::XS; # AnyEvent::I3 depends on it, too.
|
||||
|
||||
binmode STDOUT, ':utf8';
|
||||
binmode STDERR, ':utf8';
|
||||
|
||||
# Close superfluous file descriptors which were passed by running in a VIM
|
||||
# subshell or situations like that.
|
||||
AnyEvent::Util::close_all_fds_except(0, 1, 2);
|
||||
@ -78,7 +82,7 @@ my @binaries = qw(
|
||||
);
|
||||
|
||||
foreach my $binary (@binaries) {
|
||||
die "$binary executable not found" unless -e $binary;
|
||||
die "$binary executable not found, did you run “make”?" unless -e $binary;
|
||||
die "$binary is not an executable" unless -x $binary;
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,10 @@ sub activate_i3 {
|
||||
$ENV{LISTEN_FDS} = 1;
|
||||
delete $ENV{DESKTOP_STARTUP_ID};
|
||||
delete $ENV{I3SOCK};
|
||||
# $SHELL could be set to fish, which will horribly break running shell
|
||||
# commands via i3’s exec feature. This happened e.g. when having
|
||||
# “set-option -g default-shell "/usr/bin/fish"” in ~/.tmux.conf
|
||||
delete $ENV{SHELL};
|
||||
unless ($args{dont_create_temp_dir}) {
|
||||
$ENV{XDG_RUNTIME_DIR} = '/tmp/i3-testsuite/';
|
||||
mkdir $ENV{XDG_RUNTIME_DIR};
|
||||
|
@ -529,10 +529,19 @@ sub get_ws_content {
|
||||
|
||||
Returns the container ID of the currently focused container on C<$workspace>.
|
||||
|
||||
Note that the container ID is B<not> the X11 window ID, so comparing the result
|
||||
of C<get_focused> with a window's C<< ->{id} >> property does B<not> work.
|
||||
|
||||
my $ws = fresh_workspace;
|
||||
my $first_window = open_window;
|
||||
my $first_id = get_focused();
|
||||
|
||||
my $second_window = open_window;
|
||||
is(get_focused($ws), $second_window, 'second window focused');
|
||||
my $second_id = get_focused();
|
||||
|
||||
cmd 'focus left';
|
||||
|
||||
is(get_focused($ws), $first_id, 'second window focused');
|
||||
|
||||
=cut
|
||||
sub get_focused {
|
||||
|
@ -79,6 +79,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
@ -17,14 +17,42 @@
|
||||
use i3test i3_autostart => 0;
|
||||
use List::Util qw(first);
|
||||
|
||||
my $_NET_WM_STATE_REMOVE = 0;
|
||||
my $_NET_WM_STATE_ADD = 1;
|
||||
my $_NET_WM_STATE_TOGGLE = 2;
|
||||
|
||||
sub set_urgency {
|
||||
my ($win, $urgent_flag, $type) = @_;
|
||||
if ($type == 1) {
|
||||
$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
|
||||
32, # format
|
||||
0, # sequence
|
||||
$win->id, # window
|
||||
$x->atom(name => '_NET_WM_STATE')->id, # message type
|
||||
($urgent_flag ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0]
|
||||
$x->atom(name => '_NET_WM_STATE_DEMANDS_ATTENTION')->id, # data32[1]
|
||||
0, # data32[2]
|
||||
0, # data32[3]
|
||||
0; # data32[4]
|
||||
|
||||
$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
force_display_urgency_hint 0ms
|
||||
EOT
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $type;
|
||||
for ($type = 1; $type <= 2; $type++) {
|
||||
my $pid = launch_with_config($config);
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
#####################################################################
|
||||
@ -44,7 +72,7 @@ is(@urgent, 0, 'no window got the urgent flag');
|
||||
#####################################################################
|
||||
# Add the urgency hint, switch to a different workspace and back again
|
||||
#####################################################################
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
my @content = @{get_ws_content($tmp)};
|
||||
@ -61,7 +89,7 @@ cmd '[id="' . $top->id . '"] focus';
|
||||
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
|
||||
is(@urgent, 0, 'no window got the urgent flag after focusing');
|
||||
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
|
||||
@ -76,7 +104,7 @@ ok(!$ws->{urgent}, 'urgent flag not set on workspace');
|
||||
|
||||
my $otmp = fresh_workspace;
|
||||
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
$ws = get_ws($tmp);
|
||||
@ -97,7 +125,7 @@ my $different_window = open_window;
|
||||
is($x->input_focus, $different_window->id, 'new window focused');
|
||||
|
||||
# Add the urgency hint on the other window.
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
# Now try to switch to that window and see if focus changes.
|
||||
@ -113,20 +141,20 @@ is($x->input_focus, $top->id, 'urgent window focused');
|
||||
cmd "workspace $otmp";
|
||||
is($x->input_focus, $different_window->id, 'new window focused again');
|
||||
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
$bottom->add_hint('urgency');
|
||||
set_urgency($bottom, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
cmd '[urgent=latest] focus';
|
||||
is($x->input_focus, $bottom->id, 'latest urgent window focused');
|
||||
$bottom->delete_hint('urgency');
|
||||
set_urgency($bottom, 0, $type);
|
||||
sync_with_i3;
|
||||
|
||||
cmd '[urgent=latest] focus';
|
||||
is($x->input_focus, $top->id, 'second urgent window focused');
|
||||
$top->delete_hint('urgency');
|
||||
set_urgency($top, 0, $type);
|
||||
sync_with_i3;
|
||||
|
||||
################################################################################
|
||||
@ -137,20 +165,20 @@ sync_with_i3;
|
||||
cmd "workspace $otmp";
|
||||
is($x->input_focus, $different_window->id, 'new window focused again');
|
||||
|
||||
$top->add_hint('urgency');
|
||||
set_urgency($top, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
$bottom->add_hint('urgency');
|
||||
set_urgency($bottom, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
cmd '[urgent=oldest] focus';
|
||||
is($x->input_focus, $top->id, 'oldest urgent window focused');
|
||||
$top->delete_hint('urgency');
|
||||
set_urgency($top, 0, $type);
|
||||
sync_with_i3;
|
||||
|
||||
cmd '[urgent=oldest] focus';
|
||||
is($x->input_focus, $bottom->id, 'oldest urgent window focused');
|
||||
$bottom->delete_hint('urgency');
|
||||
set_urgency($bottom, 0, $type);
|
||||
sync_with_i3;
|
||||
|
||||
################################################################################
|
||||
@ -190,8 +218,8 @@ is($urgent, 0, 'no window got the urgent flag');
|
||||
|
||||
cmd '[id="' . $win2->id . '"] focus';
|
||||
sync_with_i3;
|
||||
$win5->add_hint('urgency');
|
||||
$win6->add_hint('urgency');
|
||||
set_urgency($win5, 1, $type);
|
||||
set_urgency($win6, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
# we should have 5 urgent cons. win5, win6 and their 3 split parents.
|
||||
@ -224,13 +252,33 @@ my $floating_win = open_floating_window;
|
||||
# switch away
|
||||
fresh_workspace;
|
||||
|
||||
$floating_win->add_hint('urgency');
|
||||
set_urgency($floating_win, 1, $type);
|
||||
sync_with_i3;
|
||||
|
||||
cmd "workspace $tmp";
|
||||
|
||||
does_i3_live;
|
||||
|
||||
###############################################################################
|
||||
# Check if the urgency hint is still set when the urgent window is killed
|
||||
###############################################################################
|
||||
|
||||
my $ws1 = fresh_workspace;
|
||||
my $ws2 = fresh_workspace;
|
||||
cmd "workspace $ws1";
|
||||
my $w1 = open_window;
|
||||
my $w2 = open_window;
|
||||
cmd "workspace $ws2";
|
||||
sync_with_i3;
|
||||
set_urgency($w1, 1, $type);
|
||||
sync_with_i3;
|
||||
cmd '[id="' . $w1->id . '"] kill';
|
||||
sync_with_i3;
|
||||
my $w = get_ws($ws1);
|
||||
is($w->{urgent}, 0, 'Urgent flag no longer set after killing the window ' .
|
||||
'from another workspace');
|
||||
|
||||
exit_gracefully($pid);
|
||||
}
|
||||
|
||||
done_testing;
|
||||
|
@ -158,4 +158,24 @@ is(get_output_content()->{layout}, 'splith', 'content container layout ok');
|
||||
cmd 'layout stacked';
|
||||
is(get_output_content()->{layout}, 'splith', 'content container layout still ok');
|
||||
|
||||
######################################################################
|
||||
# Splitting a workspace that has more than one child
|
||||
######################################################################
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
cmd 'open';
|
||||
cmd 'open';
|
||||
cmd 'focus parent';
|
||||
cmd 'split v';
|
||||
cmd 'open';
|
||||
|
||||
my $content = get_ws_content($tmp);
|
||||
my $fst = $content->[0];
|
||||
my $snd = $content->[1];
|
||||
|
||||
is(@{$content}, 2, 'two containers on workspace');
|
||||
is(@{$fst->{nodes}}, 2, 'first child has two children');
|
||||
is(@{$snd->{nodes}}, 0, 'second child has no children');
|
||||
|
||||
done_testing;
|
||||
|
@ -23,6 +23,7 @@ my $i3 = i3(get_socket_path());
|
||||
# We move the pointer out of our way to avoid a bug where the focus will
|
||||
# be set to the window under the cursor
|
||||
$x->root->warp_pointer(0, 0);
|
||||
sync_with_i3;
|
||||
|
||||
sub move_workspace_test {
|
||||
my ($movecmd) = @_;
|
||||
|
@ -98,6 +98,7 @@ my $workspaces = get_workspace_names;
|
||||
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
|
||||
|
||||
$window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
|
||||
ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
|
||||
@ -157,6 +158,7 @@ $workspaces = get_workspace_names;
|
||||
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
|
||||
|
||||
$window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
my $content = get_ws($tmp);
|
||||
ok(@{$content->{nodes}} == 0, 'no tiling cons');
|
||||
@ -205,6 +207,7 @@ is(@docked, 0, 'one dock client yet');
|
||||
$window = open_special(
|
||||
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
|
||||
);
|
||||
sync_with_i3;
|
||||
|
||||
$content = get_ws($tmp);
|
||||
ok(@{$content->{nodes}} == 0, 'no tiling cons');
|
||||
|
@ -73,6 +73,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
ok(get_ws($tmp)->{focused}, 'current workspace focused');
|
||||
|
||||
my $window = open_special;
|
||||
sync_with_i3;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
|
||||
ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
|
||||
|
@ -21,6 +21,10 @@ use i3test;
|
||||
use POSIX qw(mkfifo);
|
||||
use File::Temp qw(:POSIX);
|
||||
|
||||
SKIP: {
|
||||
|
||||
skip "X11::XCB too old (need >= 0.07)", 24 if $X11::XCB::VERSION < 0.07;
|
||||
|
||||
use ExtUtils::PkgConfig;
|
||||
|
||||
# setup dependency on libstartup-notification using pkg-config
|
||||
@ -42,16 +46,10 @@ static SnDisplay *sndisplay;
|
||||
static SnLauncheeContext *ctx;
|
||||
static xcb_connection_t *conn;
|
||||
|
||||
// TODO: this should use $x
|
||||
void init_ctx() {
|
||||
int screen;
|
||||
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
errx(1, "x11 conn failed");
|
||||
|
||||
printf("screen = %d\n", screen);
|
||||
void init_ctx(void *connptr) {
|
||||
conn = (xcb_connection_t*)connptr;
|
||||
sndisplay = sn_xcb_display_new(conn, NULL, NULL);
|
||||
ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
|
||||
ctx = sn_launchee_context_new_from_environment(sndisplay, 0);
|
||||
}
|
||||
|
||||
const char *get_startup_id() {
|
||||
@ -101,7 +99,7 @@ isnt($startup_id, '', 'startup_id not empty');
|
||||
$ENV{DESKTOP_STARTUP_ID} = $startup_id;
|
||||
|
||||
# Create a new libstartup-notification launchee context
|
||||
init_ctx();
|
||||
init_ctx($x->get_xcb_conn());
|
||||
|
||||
# Make sure the context was set up successfully
|
||||
is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
|
||||
@ -211,5 +209,6 @@ close($fh);
|
||||
unlink($tmp);
|
||||
|
||||
is($startup_id, '', 'startup_id empty');
|
||||
}
|
||||
|
||||
done_testing;
|
||||
|
@ -93,8 +93,7 @@ is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
|
||||
################################################################################
|
||||
# 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
|
||||
# workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The window’s size
|
||||
# and position will be changed (once!) on the next 'scratchpad show' and the
|
||||
# flag will be changed to SCRATCHPAD_CHANGED.
|
||||
# and position will be changed on the next 'scratchpad show'.
|
||||
################################################################################
|
||||
|
||||
my ($nodes, $focus) = get_ws_content($tmp);
|
||||
@ -165,10 +164,33 @@ $__i3_scratch = get_ws('__i3_scratch');
|
||||
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
|
||||
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
|
||||
|
||||
is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
|
||||
################################################################################
|
||||
# 6: Resizing the window should disable auto centering on scratchpad show
|
||||
################################################################################
|
||||
|
||||
cmd 'scratchpad show';
|
||||
|
||||
$ws = get_ws($tmp);
|
||||
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'fresh',
|
||||
'scratchpad_state fresh');
|
||||
|
||||
cmd 'resize grow width 10 px';
|
||||
cmd 'scratchpad show';
|
||||
cmd 'scratchpad show';
|
||||
|
||||
$ws = get_ws($tmp);
|
||||
$scratchrect = $ws->{floating_nodes}->[0]->{rect};
|
||||
$outputrect = $output->{rect};
|
||||
|
||||
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed',
|
||||
'scratchpad_state changed');
|
||||
is($scratchrect->{width}, $outputrect->{width} * 0.5 + 10, 'scratch width is 50% + 10px');
|
||||
|
||||
cmd 'resize shrink width 10 px';
|
||||
cmd 'scratchpad show';
|
||||
|
||||
################################################################################
|
||||
# 6: Verify that repeated 'scratchpad show' cycle through the stack, that is,
|
||||
# 7: Verify that repeated 'scratchpad show' cycle through the stack, that is,
|
||||
# toggling a visible window should insert it at the bottom of the stack of the
|
||||
# __i3_scratch workspace.
|
||||
################################################################################
|
||||
@ -216,39 +238,6 @@ cmd 'scratchpad show';
|
||||
|
||||
isnt(get_focused($tmp), $fresh_id, 'focus changed');
|
||||
|
||||
################################################################################
|
||||
# 7: Verify that using scratchpad show with criteria works as expected:
|
||||
# When matching a scratchpad window which is visible, it should hide it.
|
||||
# When matching a scratchpad window which is on __i3_scratch, it should show it.
|
||||
# When matching a non-scratchpad window, it should be a no-op.
|
||||
################################################################################
|
||||
|
||||
# Verify that using 'scratchpad show' without any matching windows is a no-op.
|
||||
$old_focus = get_focused($tmp);
|
||||
|
||||
cmd '[title="nomatch"] scratchpad show';
|
||||
|
||||
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
|
||||
|
||||
# Verify that we can use criteria to show a scratchpad window.
|
||||
cmd '[title="scratch-match"] scratchpad show';
|
||||
|
||||
my $scratch_focus = get_focused($tmp);
|
||||
isnt($scratch_focus, $old_focus, 'matching criteria works');
|
||||
|
||||
cmd '[title="scratch-match"] scratchpad show';
|
||||
|
||||
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
|
||||
is(get_focused($tmp), $old_focus, 'focus restored');
|
||||
|
||||
# Verify that we cannot use criteria to show a non-scratchpad window.
|
||||
my $tmp2 = fresh_workspace;
|
||||
my $non_scratch_window = open_window(name => 'non-scratch');
|
||||
cmd "workspace $tmp";
|
||||
is(get_focused($tmp), $old_focus, 'focus still ok');
|
||||
cmd '[title="non-match"] scratchpad show';
|
||||
is(get_focused($tmp), $old_focus, 'focus unchanged');
|
||||
|
||||
################################################################################
|
||||
# 8: Show it, move it around, hide it. Verify that the position is retained
|
||||
# when showing it again.
|
||||
@ -369,6 +358,92 @@ verify_scratchpad_move_multiple_win(0);
|
||||
$tmp = fresh_workspace;
|
||||
verify_scratchpad_move_multiple_win(1);
|
||||
|
||||
################################################################################
|
||||
# 12: open a scratchpad window on a workspace, switch to another workspace and
|
||||
# call 'scratchpad show' again
|
||||
################################################################################
|
||||
|
||||
sub verify_scratchpad_move_with_visible_scratch_con {
|
||||
my ($first, $second, $cross_output) = @_;
|
||||
|
||||
cmd "workspace $first";
|
||||
|
||||
my $window1 = open_window;
|
||||
cmd 'move scratchpad';
|
||||
|
||||
my $window2 = open_window;
|
||||
cmd 'move scratchpad';
|
||||
|
||||
# this should bring up window 1
|
||||
cmd 'scratchpad show';
|
||||
|
||||
my $ws = get_ws($first);
|
||||
is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
|
||||
is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
|
||||
|
||||
# this should bring up window 1
|
||||
cmd "workspace $second";
|
||||
cmd 'scratchpad show';
|
||||
is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
|
||||
|
||||
my $ws2 = get_ws($second);
|
||||
is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
|
||||
unless ($cross_output) {
|
||||
ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
|
||||
} else {
|
||||
$ws = get_ws($first);
|
||||
is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
|
||||
}
|
||||
|
||||
# hide window 1 again
|
||||
cmd 'move scratchpad';
|
||||
|
||||
# this should bring up window 2
|
||||
cmd "workspace $first";
|
||||
cmd 'scratchpad show';
|
||||
is($x->input_focus, $window2->id, "showed the correct scratchpad window");
|
||||
}
|
||||
|
||||
# let's clear the scratchpad first
|
||||
sub clear_scratchpad {
|
||||
while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
|
||||
cmd 'scratchpad show';
|
||||
cmd 'kill';
|
||||
}
|
||||
}
|
||||
|
||||
clear_scratchpad;
|
||||
is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
|
||||
|
||||
my ($first, $second);
|
||||
$first = fresh_workspace;
|
||||
$second = fresh_workspace;
|
||||
|
||||
verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
|
||||
does_i3_live;
|
||||
|
||||
|
||||
################################################################################
|
||||
# 13: Test whether scratchpad show moves focus to the scratchpad window
|
||||
# when another window on the same workspace has focus
|
||||
################################################################################
|
||||
|
||||
clear_scratchpad;
|
||||
my $ws = fresh_workspace;
|
||||
|
||||
open_window;
|
||||
my $scratch = get_focused($ws);
|
||||
cmd 'move scratchpad';
|
||||
cmd 'scratchpad show';
|
||||
|
||||
open_window;
|
||||
my $not_scratch = get_focused($ws);
|
||||
is(get_focused($ws), $not_scratch, 'not scratch window has focus');
|
||||
|
||||
cmd 'scratchpad show';
|
||||
|
||||
is(get_focused($ws), $scratch, 'scratchpad is focused');
|
||||
|
||||
# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
|
||||
|
||||
done_testing;
|
||||
|
@ -146,6 +146,7 @@ $window = open_floating_window(rect => [ 0, 0, 100, 100 ]);
|
||||
cmd 'border none';
|
||||
cmd 'resize shrink height 80px or 80ppt';
|
||||
cmd 'resize shrink width 80px or 80ppt';
|
||||
sync_with_i3;
|
||||
$rect = $window->rect;
|
||||
is($rect->{width}, 60, 'width = 60');
|
||||
is($rect->{height}, 50, 'height = 50');
|
||||
@ -170,6 +171,7 @@ $window = open_floating_window(rect => [ 200, 200, 50, 50 ]);
|
||||
cmd 'border none';
|
||||
cmd 'resize grow height 100px or 100ppt';
|
||||
cmd 'resize grow width 100px or 100ppt';
|
||||
sync_with_i3;
|
||||
$rect = $window->rect;
|
||||
is($rect->{width}, 100, 'width = 100');
|
||||
is($rect->{height}, 100, 'height = 100');
|
||||
@ -177,6 +179,7 @@ is($rect->{height}, 100, 'height = 100');
|
||||
my $old_x = $rect->{x};
|
||||
my $old_y = $rect->{y};
|
||||
cmd 'resize grow up 10px or 10ppt';
|
||||
sync_with_i3;
|
||||
$rect = $window->rect;
|
||||
is($rect->{x}, $old_x, 'window did not move when trying to resize');
|
||||
is($rect->{y}, $old_y, 'window did not move when trying to resize');
|
||||
|
@ -26,7 +26,7 @@ sub parser_calls {
|
||||
|
||||
my $stdout;
|
||||
run [ '../test.config_parser', $command ],
|
||||
'>&-',
|
||||
'>/dev/null',
|
||||
'2>', \$stdout;
|
||||
# TODO: use a timeout, so that we can error out if it doesn’t terminate
|
||||
|
||||
@ -144,6 +144,27 @@ is(parser_calls($config),
|
||||
$expected,
|
||||
'floating_minimum_size ok');
|
||||
|
||||
################################################################################
|
||||
# popup_during_fullscreen
|
||||
################################################################################
|
||||
|
||||
$config = <<'EOT';
|
||||
popup_during_fullscreen ignore
|
||||
popup_during_fullscreen leave_fullscreen
|
||||
popup_during_fullscreen SMArt
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
cfg_popup_during_fullscreen(ignore)
|
||||
cfg_popup_during_fullscreen(leave_fullscreen)
|
||||
cfg_popup_during_fullscreen(smart)
|
||||
EOT
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'popup_during_fullscreen ok');
|
||||
|
||||
|
||||
################################################################################
|
||||
# floating_modifier
|
||||
################################################################################
|
||||
@ -391,8 +412,11 @@ hide_edge_border both
|
||||
client.focused #4c7899 #285577 #ffffff #2e9ef4
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
my $expected_all_tokens = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
EOT
|
||||
|
||||
my $expected_end = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: hide_edge_border both
|
||||
ERROR: CONFIG: ^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -400,6 +424,8 @@ ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4
|
||||
cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4)
|
||||
EOT
|
||||
|
||||
$expected = $expected_all_tokens . $expected_end;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'errors dont harm subsequent statements');
|
||||
@ -438,9 +464,11 @@ unknown qux
|
||||
# this should not show up
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
my $expected_head = <<'EOT';
|
||||
cfg_font(foobar)
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
EOT
|
||||
|
||||
my $expected_tail = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 3: font foobar
|
||||
ERROR: CONFIG: Line 4:
|
||||
@ -450,6 +478,8 @@ ERROR: CONFIG: Line 6:
|
||||
ERROR: CONFIG: Line 7: # yay
|
||||
EOT
|
||||
|
||||
$expected = $expected_head . $expected_all_tokens . $expected_tail;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'error message (2+2 context) ok');
|
||||
@ -462,13 +492,14 @@ $config = <<'EOT';
|
||||
unknown qux
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
$expected_tail = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: unknown qux
|
||||
ERROR: CONFIG: ^^^^^^^^^^^
|
||||
EOT
|
||||
|
||||
$expected = $expected_all_tokens . $expected_tail;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'error message (0+0 context) ok');
|
||||
@ -482,14 +513,15 @@ $config = <<'EOT';
|
||||
unknown qux
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
$expected_tail = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: # context before
|
||||
ERROR: CONFIG: Line 2: unknown qux
|
||||
ERROR: CONFIG: ^^^^^^^^^^^
|
||||
EOT
|
||||
|
||||
$expected = $expected_all_tokens . $expected_tail;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'error message (1+0 context) ok');
|
||||
@ -503,14 +535,15 @@ unknown qux
|
||||
# context after
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
$expected_tail = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: unknown qux
|
||||
ERROR: CONFIG: ^^^^^^^^^^^
|
||||
ERROR: CONFIG: Line 2: # context after
|
||||
EOT
|
||||
|
||||
$expected = $expected_all_tokens . $expected_tail;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'error message (0+1 context) ok');
|
||||
@ -525,8 +558,7 @@ unknown qux
|
||||
# context 2 after
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent'
|
||||
$expected_tail = <<'EOT';
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: unknown qux
|
||||
ERROR: CONFIG: ^^^^^^^^^^^
|
||||
@ -534,6 +566,8 @@ ERROR: CONFIG: Line 2: # context after
|
||||
ERROR: CONFIG: Line 3: # context 2 after
|
||||
EOT
|
||||
|
||||
$expected = $expected_all_tokens . $expected_tail;
|
||||
|
||||
is(parser_calls($config),
|
||||
$expected,
|
||||
'error message (0+2 context) ok');
|
||||
|
58
testcases/t/202-scratchpad-criteria.t
Normal file
58
testcases/t/202-scratchpad-criteria.t
Normal file
@ -0,0 +1,58 @@
|
||||
#!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)
|
||||
#
|
||||
# Verifies that using criteria to address scratchpad windows works.
|
||||
use i3test;
|
||||
|
||||
################################################################################
|
||||
# Verify that using scratchpad show with criteria works as expected:
|
||||
# When matching a scratchpad window which is visible, it should hide it.
|
||||
# When matching a scratchpad window which is on __i3_scratch, it should show it.
|
||||
# When matching a non-scratchpad window, it should be a no-op.
|
||||
################################################################################
|
||||
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
my $third_window = open_window(name => 'scratch-match');
|
||||
cmd 'move scratchpad';
|
||||
|
||||
# Verify that using 'scratchpad show' without any matching windows is a no-op.
|
||||
my $old_focus = get_focused($tmp);
|
||||
|
||||
cmd '[title="nomatch"] scratchpad show';
|
||||
|
||||
is(get_focused($tmp), $old_focus, 'non-matching criteria have no effect');
|
||||
|
||||
# Verify that we can use criteria to show a scratchpad window.
|
||||
cmd '[title="scratch-match"] scratchpad show';
|
||||
|
||||
my $scratch_focus = get_focused($tmp);
|
||||
isnt($scratch_focus, $old_focus, 'matching criteria works');
|
||||
|
||||
cmd '[title="scratch-match"] scratchpad show';
|
||||
|
||||
isnt(get_focused($tmp), $scratch_focus, 'matching criteria works');
|
||||
is(get_focused($tmp), $old_focus, 'focus restored');
|
||||
|
||||
# Verify that we cannot use criteria to show a non-scratchpad window.
|
||||
my $tmp2 = fresh_workspace;
|
||||
my $non_scratch_window = open_window(name => 'non-scratch');
|
||||
cmd "workspace $tmp";
|
||||
is(get_focused($tmp), $old_focus, 'focus still ok');
|
||||
cmd '[title="non-match"] scratchpad show';
|
||||
is(get_focused($tmp), $old_focus, 'focus unchanged');
|
||||
|
||||
done_testing;
|
65
testcases/t/204-regress-scratchpad-move.t
Normal file
65
testcases/t/204-regress-scratchpad-move.t
Normal file
@ -0,0 +1,65 @@
|
||||
#!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)
|
||||
#
|
||||
# Moves the last window of a workspace to the scratchpad. The workspace will be
|
||||
# cleaned up and previously, the subsequent focusing of a destroyed container
|
||||
# would crash i3.
|
||||
# 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 $tmp2 = fresh_workspace;
|
||||
|
||||
cmd '[class="special"] move scratchpad';
|
||||
|
||||
does_i3_live;
|
||||
|
||||
done_testing;
|
49
testcases/t/205-ipc-windows.t
Normal file
49
testcases/t/205-ipc-windows.t
Normal file
@ -0,0 +1,49 @@
|
||||
#!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)
|
||||
|
||||
use i3test;
|
||||
|
||||
SKIP: {
|
||||
|
||||
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
|
||||
|
||||
my $i3 = i3(get_socket_path());
|
||||
$i3->connect()->recv;
|
||||
|
||||
################################
|
||||
# Window event
|
||||
################################
|
||||
|
||||
# Events
|
||||
|
||||
my $new = AnyEvent->condvar;
|
||||
$i3->subscribe({
|
||||
window => sub {
|
||||
my ($event) = @_;
|
||||
$new->send($event->{change} eq 'new');
|
||||
}
|
||||
})->recv;
|
||||
|
||||
open_window;
|
||||
|
||||
my $t;
|
||||
$t = AnyEvent->timer(after => 0.5, cb => sub { $new->send(0); });
|
||||
|
||||
ok($new->recv, 'Window "new" event received');
|
||||
|
||||
}
|
||||
|
||||
done_testing;
|
@ -61,8 +61,18 @@ open_window;
|
||||
# output 2: 2
|
||||
cmd 'workspace 1';
|
||||
cmd 'workspace next';
|
||||
# We need to sync after changing focus to a different output to wait for the
|
||||
# EnterNotify to be processed, otherwise it will be processed at some point
|
||||
# later in time and mess up our subsequent tests.
|
||||
sync_with_i3;
|
||||
|
||||
is(focused_ws, '2', 'workspace 2 focused');
|
||||
cmd 'workspace next';
|
||||
# We need to sync after changing focus to a different output to wait for the
|
||||
# EnterNotify to be processed, otherwise it will be processed at some point
|
||||
# later in time and mess up our subsequent tests.
|
||||
sync_with_i3;
|
||||
|
||||
is(focused_ws, '5', 'workspace 5 focused');
|
||||
|
||||
################################################################################
|
||||
@ -81,11 +91,9 @@ cmd 'workspace prev_on_output';
|
||||
is(focused_ws, '1', 'workspace 1 focused');
|
||||
|
||||
cmd 'workspace 2';
|
||||
|
||||
# XXX: This is to avoid EnterNotifies changing the focus. Not sure why they
|
||||
# appear sometimes in the first place. Only happens when running the full
|
||||
# testsuite.
|
||||
$x->root->warp_pointer(1025, 0);
|
||||
# We need to sync after changing focus to a different output to wait for the
|
||||
# EnterNotify to be processed, otherwise it will be processed at some point
|
||||
# later in time and mess up our subsequent tests.
|
||||
sync_with_i3;
|
||||
|
||||
cmd 'workspace prev_on_output';
|
||||
|
@ -114,6 +114,16 @@ cmd 'move workspace to output left';
|
||||
($x0, $x1) = workspaces_per_screen();
|
||||
ok('5' ~~ @$x0, 'workspace 5 back on fake-0');
|
||||
|
||||
# Verify that wrapping works
|
||||
cmd 'move workspace to output left';
|
||||
($x0, $x1) = workspaces_per_screen();
|
||||
ok('5' ~~ @$x1, 'workspace 5 on fake-1');
|
||||
|
||||
# Put workspace 5 where it should
|
||||
cmd 'move workspace to output left';
|
||||
($x0, $x1) = workspaces_per_screen();
|
||||
ok('5' ~~ @$x0, 'workspace 5 on fake-0 again');
|
||||
|
||||
################################################################################
|
||||
# Verify that coordinates of floating windows are fixed correctly when moving a
|
||||
# workspace to a different output.
|
||||
|
@ -39,22 +39,22 @@ cmd 'floating toggle';
|
||||
|
||||
# Focus screen 1
|
||||
$x->root->warp_pointer(1025, 0);
|
||||
my $s1_ws = fresh_workspace;
|
||||
sync_with_i3;
|
||||
my $s1_ws = fresh_workspace;
|
||||
|
||||
my $fourth = open_window;
|
||||
|
||||
# Focus screen 2
|
||||
$x->root->warp_pointer(0, 769);
|
||||
my $s2_ws = fresh_workspace;
|
||||
sync_with_i3;
|
||||
my $s2_ws = fresh_workspace;
|
||||
|
||||
my $fifth = open_window;
|
||||
|
||||
# Focus screen 3
|
||||
$x->root->warp_pointer(1025, 769);
|
||||
my $s3_ws = fresh_workspace;
|
||||
sync_with_i3;
|
||||
my $s3_ws = fresh_workspace;
|
||||
|
||||
my $sixth = open_window;
|
||||
my $seventh = open_window;
|
||||
|
@ -14,9 +14,10 @@
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Regression test: Verify that focus is correct after moving a floating window
|
||||
# to a workspace on a different visible output.
|
||||
# Bug still in: 4.3-83-ge89a25f
|
||||
# Verifies that moving containers wraps across outputs.
|
||||
# E.g. when you have a container on the right output and you move it to the
|
||||
# right, it should appear on the left output.
|
||||
# Bug still in: 4.4-106-g3cd4b8c
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# Ensure the pointer is at (0, 0) so that we really start on the first
|
||||
@ -29,17 +30,26 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0,1024x768+1024+0
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $left_ws = fresh_workspace(output => 0);
|
||||
open_window;
|
||||
my $right = fresh_workspace(output => 1);
|
||||
my $left = fresh_workspace(output => 0);
|
||||
|
||||
my $right_ws = fresh_workspace(output => 1);
|
||||
open_window;
|
||||
my $right_float = open_floating_window;
|
||||
my $win = open_window;
|
||||
|
||||
cmd "move workspace $left_ws";
|
||||
is($x->input_focus, $right_float->id, 'floating window still focused');
|
||||
is_num_children($left, 1, 'one container on left workspace');
|
||||
|
||||
cmd 'move container to output right';
|
||||
cmd 'focus output right';
|
||||
|
||||
is_num_children($left, 0, 'no containers on left workspace');
|
||||
is_num_children($right, 1, 'one container on right workspace');
|
||||
|
||||
cmd 'move container to output right';
|
||||
|
||||
is_num_children($left, 1, 'one container on left workspace');
|
||||
is_num_children($right, 0, 'no containers on right workspace');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
97
testcases/t/513-move-workspace.t
Normal file
97
testcases/t/513-move-workspace.t
Normal file
@ -0,0 +1,97 @@
|
||||
#!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 whether moving workspaces between outputs works correctly.
|
||||
use i3test i3_autostart => 0;
|
||||
use List::Util qw(first);
|
||||
|
||||
# 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
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
sub workspaces_per_screen {
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $tree = $i3->get_tree->recv;
|
||||
my @outputs = @{$tree->{nodes}};
|
||||
|
||||
my $fake0 = first { $_->{name} eq 'fake-0' } @outputs;
|
||||
my $fake0_content = first { $_->{type} == 2 } @{$fake0->{nodes}};
|
||||
|
||||
my $fake1 = first { $_->{name} eq 'fake-1' } @outputs;
|
||||
my $fake1_content = first { $_->{type} == 2 } @{$fake1->{nodes}};
|
||||
|
||||
my @fake0_workspaces = map { $_->{name} } @{$fake0_content->{nodes}};
|
||||
my @fake1_workspaces = map { $_->{name} } @{$fake1_content->{nodes}};
|
||||
|
||||
return \@fake0_workspaces, \@fake1_workspaces;
|
||||
}
|
||||
|
||||
# Switch to temporary workspaces on both outputs so the numbers are free.
|
||||
my $tmp_right = fresh_workspace(output => 1);
|
||||
my $tmp_left = fresh_workspace(output => 0);
|
||||
|
||||
cmd 'workspace 1';
|
||||
# Keep that workspace open.
|
||||
my $win1 = open_window;
|
||||
|
||||
cmd 'workspace 5';
|
||||
# Keep that workspace open.
|
||||
open_window;
|
||||
|
||||
cmd "workspace $tmp_right";
|
||||
cmd 'workspace 2';
|
||||
# Keep that workspace open.
|
||||
open_window;
|
||||
|
||||
my ($x0, $x1) = workspaces_per_screen();
|
||||
is_deeply($x0, [ '1', '5' ], 'workspace 1 and 5 on fake-0');
|
||||
is_deeply($x1, [ '2' ], 'workspace 2 on fake-1');
|
||||
|
||||
cmd 'workspace 1';
|
||||
|
||||
my ($nodes, $focus) = get_ws_content('1');
|
||||
is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
|
||||
|
||||
cmd 'move workspace next';
|
||||
cmd '[id="' . $win1->id . '"] focus';
|
||||
|
||||
($nodes, $focus) = get_ws_content('2');
|
||||
is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 2 after moving');
|
||||
|
||||
cmd 'move workspace prev';
|
||||
cmd '[id="' . $win1->id . '"] focus';
|
||||
|
||||
($nodes, $focus) = get_ws_content('1');
|
||||
is($nodes->[0]->{window}, $win1->id, 'window 1 on workspace 1');
|
||||
|
||||
cmd 'move workspace next_on_output';
|
||||
cmd '[id="' . $win1->id . '"] focus';
|
||||
|
||||
($nodes, $focus) = get_ws_content('5');
|
||||
is($nodes->[1]->{window}, $win1->id, 'window 1 on workspace 5 after moving');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
Loading…
Reference in New Issue
Block a user