*.gcno
testcases/testsuite-*
testcases/latest
+testcases/Makefile
*.output
*.tab.*
*.yy.c
src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
echo "[i3] LEX $<"
- flex -i -o$(@:.o=.c) $<
+ $(FLEX) -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS}
echo "[i3] LEX $<"
- flex -Pcmdyy -i -o$(@:.o=.c) $<
+ $(FLEX) -Pcmdyy -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
echo "[i3] YACC $<"
- bison --debug --verbose -b $(basename $< .y) -d $<
+ $(BISON) --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
src/cmdparse.y.o: src/cmdparse.y ${HEADERS}
echo "[i3] YACC $<"
- bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
+ $(BISON) -p cmdyy --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
DEBUG=1
COVERAGE=0
INSTALL=install
+FLEX=flex
+BISON=bison
ifndef PREFIX
PREFIX=/usr
endif
+i3-wm (4.1.1-0) unstable; urgency=low
+
+ * NOT YET RELEASED
+
+ -- Michael Stapelberg <michael@stapelberg.de> Fri, 11 Nov 2011 23:00:04 +0000
+
i3-wm (4.1-1) unstable; urgency=low
* Switch to dpkg-source 3.0 (quilt) and compat level 7
Package: i3
Architecture: any
Section: x11
-Depends: i3-wm, ${misc:Depends}
-Recommends: i3lock, suckless-tools, i3status
+Depends: i3-wm (=${binary:Version}), ${misc:Depends}
+Recommends: i3lock (>= 2.2), suckless-tools, i3status (>= 2.3)
Description: metapackage (i3 window manager, screen locker, menu, statusbar)
This metapackage installs the i3 window manager (i3-wm), the i3lock screen
locker, i3status (for system information) and suckless-tools (for dmenu).
docs/testsuite.html
docs/i3-sync-working.png
docs/i3-sync.png
+docs/tree-layout1.png
+docs/tree-layout2.png
+docs/tree-shot1.png
+docs/tree-shot2.png
+docs/tree-shot3.png
+docs/tree-shot4.png
-# distribution-specific mechanism to find the preferred terminal emulator. On
-# Debian, there is the x-terminal-emulator symlink for example.
-# Please don't touch the first line, though:
- which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+ [ -n "$TERMINAL" ] && which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+# Debian-specific: use x-terminal-emulator
+which x-terminal-emulator >/dev/null && exec x-terminal-emulator "$@"
Contains debugging functions to print unhandled X events.
src/ewmh.c::
-iFunctions to get/set certain EWMH properties easily.
+Functions to get/set certain EWMH properties easily.
src/floating.c::
Contains functions for floating mode (mostly resizing/dragging).
== Data structures
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
See include/data.h for documented data structures. The most important ones are
explained right here.
+/////////////////////////////////////////////////////////////////////////////////
+// TODO: update image
+
image:bigpicture.png[The Big Picture]
/////////////////////////////////////////////////////////////////////////////////
So, the hierarchy is:
. *X11 root window*, the root container
-. *Virtual screens* (Screen 0 in this example)
+. *Output container* (LVDS1 in this example)
. *Content container* (there are also containers for dock windows)
. *Workspaces* (Workspace 1 in this example, with horizontal orientation)
. *Split container* (vertically split)
The data type is +Con+, in all cases.
-=== Virtual screens
+=== X11 root window
+
+The X11 root window is a single window per X11 display (a display is identified
+by +:0+ or +:1+ etc.). The root window is what you draw your background image
+on. It spans all the available outputs, e.g. +VGA1+ is a specific part of the
+root window and +LVDS1+ is a specific part of the root window.
-A virtual screen (type `i3Screen`) is generated from the connected outputs
-obtained through RandR. The difference to the raw RandR outputs as seen
-when using +xrandr(1)+ is that it falls back to the lowest common resolution of
-the actual enabled outputs.
+=== Output container
+
+Every active output obtained through RandR is represented by one output
+container. Outputs are considered active when a mode is configured (meaning
+something is actually displayed on the output) and the output is not a clone.
For example, if your notebook has a screen resolution of 1280x800 px and you
connect a video projector with a resolution of 1024x768 px, set it up in clone
-mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), i3 will have
-one virtual screen.
+mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), i3 will
+reduce the resolution to the lowest common resolution and disable one of the
+cloned outputs afterwards.
However, if you configure it using +xrandr \--output VGA1 \--mode 1024x768
-\--right-of LVDS1+, i3 will generate two virtual screens. For each virtual
-screen, a new workspace will be assigned. New workspaces are created on the
-screen you are currently on.
+\--right-of LVDS1+, i3 will set both outputs active. For each output, a new
+workspace will be assigned. New workspaces are created on the output you are
+currently on.
+
+=== Content container
+
+Each output has multiple children. Two of them are dock containers which hold
+dock clients. The other one is the content container, which holds the actual
+content (workspaces) of this output.
=== Workspace
A workspace is identified by its name. Basically, you could think of
workspaces as different desks in your office, if you like the desktop
-methaphor. They just contain different sets of windows and are completely
+metaphor. They just contain different sets of windows and are completely
separate of each other. Other window managers also call this ``Virtual
desktops''.
-=== The layout table
+=== Split container
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-
-Each workspace has a table, which is just a two-dimensional dynamic array
-containing Containers (see below). This table grows and shrinks as you need it
-(by moving windows to the right you can create a new column in the table, by
-moving them to the bottom you create a new row).
+A split container is a container which holds an arbitrary amount of split
+containers or X11 window containers. It has an orientation (horizontal or
+vertical) and a layout.
-/////////////////////////////////////////////////////////////////////////////////
+Split containers (and X11 window containers, which are a subtype of split
+containers) can have different border styles.
-=== Container
+=== X11 window container
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-
-A container is the content of a table’s cell. It holds an arbitrary amount of
-windows and has a specific layout (default layout, stack layout or tabbed
-layout). Containers can consume multiple table cells by modifying their
-colspan/rowspan attribute.
-
-/////////////////////////////////////////////////////////////////////////////////
-
-=== Client
-
-A client is x11-speak for a window.
+An X11 window container holds exactly one X11 window. These are the leaf nodes
+of the layout tree, they cannot have any children.
== List/queue macros
whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for
example. Docks are handled differently, they don’t have decorations and are not
assigned to a specific container. Instead, they are positioned at the bottom
-of the screen. To get the height which needs to be reserved for the window,
-the `_NET_WM_STRUT_PARTIAL` property is used.
+or top of the screen (in the appropriate dock area containers). To get the
+height which needs to be reserved for the window, the `_NET_WM_STRUT_PARTIAL`
+property is used.
Furthermore, the list of assignments (to other workspaces, which may be on
other screens) is checked. If the window matches one of the user’s criteria,
== Rendering (src/layout.c, render_layout() and render_container())
-*********************************************************************************
-This section has not been updated for v4.0 yet, sorry! We wanted to release on
-time, but we will update this soon. Please talk to us on IRC if you need to
-know stuff *NOW* :).
-*********************************************************************************
-
-/////////////////////////////////////////////////////////////////////////////////
-
-
-There are several entry points to rendering: `render_layout()`,
-`render_workspace()` and `render_container()`. The former one calls
-`render_workspace()` for every screen, which in turn will call
-`render_container()` for every container inside its layout table. Therefore, if
-you need to render only a single container, for example because a window was
-removed, added or changed its title, you should directly call
-render_container().
-
-Rendering consists of two steps: In the first one, in `render_workspace()`, each
-container gets its position (screen offset + offset in the table) and size
-(container's width times colspan/rowspan). Then, `render_container()` is called,
-which takes different approaches, depending on the mode the container is in:
-
-=== Common parts
-
-On the frame (the window which was created around the client’s window for the
-decorations), a black rectangle is drawn as a background for windows like
-MPlayer, which do not completely fit into the frame.
-
-=== Default mode
-
-Each clients gets the container’s width and an equal amount of height.
-
-=== Stack mode
-
-In stack mode, a window containing the decorations of all windows inside the
-container is placed at the top. The currently focused window is then given the
-whole remaining space.
-
-=== Tabbed mode
-
-Tabbed mode is like stack mode, except that the window decorations are drawn
-in one single line at the top of the container.
-
-=== Window decorations
+Rendering in i3 version 4 is the step which assigns the correct sizes for
+borders, decoration windows, child windows and the stacking order of all
+windows. In a separate step (+x_push_changes()+), these changes are pushed to
+X11.
+
+Keep in mind that all these properties (+rect+, +window_rect+ and +deco_rect+)
+are temporary, meaning they will be overwritten by calling +render_con+.
+Persistent position/size information is kept in +geometry+.
+
+The entry point for every rendering operation (except for the case of moving
+floating windows around) currently is +tree_render()+ which will re-render
+everything that’s necessary (for every output, only the currently displayed
+workspace is rendered). This behavior is expected to change in the future,
+since for a lot of updates, re-rendering everything is not actually necessary.
+Focus was on getting it working correct, not getting it work very fast.
+
+What +tree_render()+ actually does is calling +render_con()+ on the root
+container and then pushing the changes to X11. The following sections talk
+about the different rendering steps, in the order of "top of the tree" (root
+container) to the bottom.
+
+=== Rendering the root container
+
+The i3 root container (`con->type == CT_ROOT`) represents the X11 root window.
+It contains one child container for every output (like LVDS1, VGA1, …), which
+is available on your computer.
+
+Rendering the root will first render all tiling windows and then all floating
+windows. This is necessary because a floating window can be positioned in such
+a way that it is visible on two different outputs. Therefore, by first
+rendering all the tiling windows (of all outputs), we make sure that floating
+windows can never be obscured by tiling windows.
+
+Essentially, though, this code path will just call +render_con()+ for every
+output and +x_raise_con(); render_con()+ for every floating window.
+
+In the special case of having a "global fullscreen" window (fullscreen mode
+spanning all outputs), a shortcut is taken and +x_raise_con(); render_con()+ is
+only called for the global fullscreen window.
+
+=== Rendering an output
+
+Output containers (`con->layout == L_OUTPUT`) represent a hardware output like
+LVDS1, VGA1, etc. An output container has three children (at the moment): One
+content container (having workspaces as children) and the top/bottom dock area
+containers.
+
+The rendering happens in the function +render_l_output()+ in the following
+steps:
+
+1. Find the content container (`con->type == CT_CON`)
+2. Get the currently visible workspace (+con_get_fullscreen_con(content,
+ CF_OUTPUT)+).
+3. If there is a fullscreened window on that workspace, directly render it and
+ return, thus ignoring the dock areas.
+4. Sum up the space used by all the dock windows (they have a variable height
+ only).
+5. Set the workspace rects (x/y/width/height) based on the position of the
+ output (stored in `con->rect`) and the usable space
+ (`con->rect.{width,height}` without the space used for dock windows).
+6. Recursively raise and render the output’s child containers (meaning dock
+ area containers and the content container).
+
+=== Rendering a workspace or split container
+
+From here on, there really is no difference anymore. All containers are of
+`con->type == CT_CON` (whether workspace or split container) and some of them
+have a `con->window`, meaning they represent an actual window instead of a
+split container.
+
+==== Default layout
+
+In default layout, containers are placed horizontally or vertically next to
+each other (depending on the `con->orientation`). If a child is a leaf node (as
+opposed to a split container) and has border style "normal", appropriate space
+will be reserved for its window decoration.
+
+==== Stacked layout
+
+In stacked layout, only the focused window is actually shown (this is achieved
+by calling +x_raise_con()+ in reverse focus order at the end of +render_con()+).
+
+The available space for the focused window is the size of the container minus
+the height of the window decoration for all windows inside this stacked
+container.
-The window decorations consist of a rectangle in the appropriate color (depends
-on whether this window is the currently focused one, the last focused one in a
-not focused container or not focused at all) forming the background.
-Afterwards, two lighter lines are drawn and the last step is drawing the
-window’s title (see WM_NAME) onto it.
+If border style is "1pixel" or "none", no window decoration height will be
+reserved (or displayed later on), unless there is more than one window inside
+the stacked container.
+
+==== Tabbed layout
+
+Tabbed layout works precisely like stacked layout, but the window decoration
+position/size is different: They are placed next to each other on a single line
+(fixed height).
+
+==== Dock area layout
+
+This is a special case. Users cannot chose the dock area layout, but it will be
+set for the dock area containers. In the dockarea layout (at the moment!),
+windows will be placed above each other.
+
+=== Rendering a window
+
+A window’s size and position will be determined in the following way:
+
+1. Subtract the border if border style is not "none" (but "normal" or "1pixel").
+2. Subtract the X11 border, if the window has an X11 border > 0.
+3. Obey the aspect ratio of the window (think MPlayer).
+4. Obey the height- and width-increments of the window (think terminal emulator
+ which can only be resized in one-line or one-character steps).
+
+== Pushing updates to X11 / Drawing
+
+A big problem with i3 before version 4 was that we just sent requests to X11
+anywhere in the source code. This was bad because nobody could understand the
+entirety of our interaction with X11, it lead to subtle bugs and a lot of edge
+cases which we had to consider all over again.
+
+Therefore, since version 4, we have a single file, +src/x.c+, which is
+responsible for repeatedly transferring parts of our tree datastructure to X11.
+
++src/x.c+ consists of multiple parts:
+
+1. The state pushing: +x_push_changes()+, which calls +x_push_node()+.
+2. State modification functions: +x_con_init+, +x_reinit+,
+ +x_reparent_child+, +x_move_win+, +x_con_kill+, +x_raise_con+, +x_set_name+
+ and +x_set_warp_to+.
+3. Expose event handling (drawing decorations): +x_deco_recurse()+ and
+ +x_draw_decoration()+.
+
+=== Pushing state to X11
+
+In general, the function +x_push_changes+ should be called to push state
+changes. Only when the scope of the state change is clearly defined (for
+example only the title of a window) and its impact is known beforehand, one can
+optimize this and call +x_push_node+ on the appropriate con directly.
+
++x_push_changes+ works in the following steps:
+
+1. Clear the eventmask for all mapped windows. This leads to not getting
+ useless ConfigureNotify or EnterNotify events which are caused by our
+ requests. In general, we only want to handle user input.
+2. Stack windows above each other, in reverse stack order (starting with the
+ most obscured/bottom window). This is relevant for floating windows which
+ can overlap each other, but also for tiling windows in stacked or tabbed
+ containers. We also update the +_NET_CLIENT_LIST_STACKING+ hint which is
+ necessary for tab drag and drop in Chromium.
+3. +x_push_node+ will be called for the root container, recursively calling
+ itself for the container’s children. This function actually pushes the
+ state, see the next paragraph.
+4. If the pointer needs to be warped to a different position (for example when
+ changing focus to a differnt output), it will be warped now.
+5. The eventmask is restored for all mapped windows.
+6. Window decorations will be rendered by calling +x_deco_recurse+ on the root
+ container, which then recursively calls itself for the children.
+7. If the input focus needs to be changed (because the user focused a different
+ window), it will be updated now.
+8. +x_push_node_unmaps+ will be called for the root container. This function
+ only pushes UnmapWindow requests. Separating the state pushing is necessary
+ to handle fullscreen windows (and workspace switches) in a smooth fashion:
+ The newly visible windows should be visible before the old windows are
+ unmapped.
+
++x_push_node+ works in the following steps:
+
+1. Update the window’s +WM_NAME+, if changed (the +WM_NAME+ is set on i3
+ containers mainly for debugging purposes).
+2. Reparents a child window into the i3 container if the container was created
+ for a specific managed window.
+3. If the size/position of the i3 container changed (due to opening a new
+ window or switching layouts for example), the window will be reconfigured.
+ Also, the pixmap which is used to draw the window decoration/border on is
+ reconfigured (pixmaps are size-dependent).
+4. Size/position for the child window is adjusted.
+5. The i3 container is mapped if it should be visible and was not yet mapped.
+ When mapping, +WM_STATE+ is set to +WM_STATE_NORMAL+. Also, the eventmask of
+ the child window is updated and the i3 container’s contents are copied from
+ the pixmap.
+6. +x_push_node+ is called recursively for all children of the current
+ container.
+
++x_push_node_unmaps+ handles the remaining case of an i3 container being
+unmapped if it should not be visible anymore. +WM_STATE+ will be set to
++WM_STATE_WITHDRAWN+.
+
+
+=== Drawing window decorations/borders/backgrounds
+
++x_draw_decoration+ draws window decorations. It is run for every leaf
+container (representing an actual X11 window) and for every non-leaf container
+which is in a stacked/tabbed container (because stacked/tabbed containers
+display a window decoration for split containers, which at the moment just says
+"another container").
+
+Then, parameters are collected to be able to determine whether this decoration
+drawing is actually necessary or was already done. This saves a substantial
+number of redraws (depending on your workload, but far over 50%).
+
+Assuming that we need to draw this decoration, we start by filling the empty
+space around the child window (think of MPlayer with a specific aspect ratio)
+in the user-configured client background color.
+
+Afterwards, we draw the appropriate border (in case of border styles "normal"
+and "1pixel") and the top bar (in case of border style "normal").
+
+The last step is drawing the window title on the top bar.
-=== Fullscreen windows
-For fullscreen windows, the `rect` (x, y, width, height) is not changed to
-allow the client to easily go back to its previous position. Instead,
-fullscreen windows are skipped when rendering.
+/////////////////////////////////////////////////////////////////////////////////
-=== Resizing containers
+== Resizing containers
By clicking and dragging the border of a container, you can resize the whole
column (respectively row) which this container is in. This is necessary to keep
-----------------------
Just send us the generated file via email.
+
+== Thought experiments
+
+In this section, we collect thought experiments, so that we don’t forget our
+thoughts about specific topics. They are not necessary to get into hacking i3,
+but if you are interested in one of the topics they cover, you should read them
+before asking us why things are the way they are or why we don’t implement
+things.
+
+=== Using cgroups per workspace
+
+cgroups (control groups) are a linux-only feature which provides the ability to
+group multiple processes. For each group, you can individually set resource
+limits, like allowed memory usage. Furthermore, and more importantly for our
+purposes, they serve as a namespace, a label which you can attach to processes
+and their children.
+
+One interesting use for cgroups is having one cgroup per workspace (or
+container, doesn’t really matter). That way, you could set different priorities
+and have a workspace for important stuff (say, writing a LaTeX document or
+programming) and a workspace for unimportant background stuff (say,
+JDownloader). Both tasks can obviously consume a lot of I/O resources, but in
+this example it doesn’t really matter if JDownloader unpacks the download a
+minute earlier or not. However, your compiler should work as fast as possible.
+Having one cgroup per workspace, you would assign more resources to the
+programming workspace.
+
+Another interesting feature is that an inherent problem of the workspace
+concept could be solved by using cgroups: When starting an application on
+workspace 1, then switching to workspace 2, you will get the application’s
+window(s) on workspace 2 instead of the one you started it on. This is because
+the window manager does not have any mapping between the process it starts (or
+gets started in any way) and the window(s) which appear.
+
+Imagine for example using dmenu: The user starts dmenu by pressing Mod+d, dmenu
+gets started with PID 3390. The user then decides to launch Firefox, which
+takes a long time. So he enters firefox into dmenu and presses enter. Firefox
+gets started with PID 4001. When it finally finishes loading, it creates an X11
+window and uses MapWindow to make it visible. This is the first time i3
+actually gets in touch with Firefox. It decides to map the window, but it has
+no way of knowing that this window (even though it has the _NET_WM_PID property
+set to 4001) belongs to the dmenu the user started before.
+
+How do cgroups help with this? Well, when pressing Mod+d to launch dmenu, i3
+would create a new cgroup, let’s call it i3-3390-1. It launches dmenu in that
+cgroup, which gets PID 3390. As before, the user enters firefox and Firefox
+gets launched with PID 4001. This time, though, the Firefox process with PID
+4001 is *also* member of the cgroup i3-3390-1 (because fork()ing in a cgroup
+retains the cgroup property). Therefore, when mapping the window, i3 can look
+up in which cgroup the process is and can establish a mapping between the
+workspace and the window.
+
+There are multiple problems with this approach:
+
+. Every application has to properly set +_NET_WM_PID+. This is acceptable and
+ patches can be written for the few applications which don’t set the hint yet.
+. It does only work on Linux, since cgroups are a Linux-only feature. Again,
+ this is acceptable.
+. The main problem is that some applications create X11 windows completely
+ independent of UNIX processes. An example for this is Chromium (or
+ gnome-terminal), which, when being started a second time, communicates with
+ the first process and lets the first process open a new window. Therefore, if
+ you have a Chromium window on workspace 2 and you are currently working on
+ workspace 3, starting +chromium+ does not lead to the desired result (the
+ window will open on workspace 2).
+
+Therefore, my conclusion is that the only proper way of fixing the "window gets
+opened on the wrong workspace" problem is in the application itself. Most
+modern applications support freedesktop startup-notifications which can be
+used for this.
i3 stores all information about the X11 outputs, workspaces and layout of the
windows on them in a tree. The root node is the X11 root window, followed by
the X11 outputs, then dock areas and a content container, then workspaces and
-finally the windows themselve. In previous versions of i3 we had multiple lists
+finally the windows themselves. In previous versions of i3 we had multiple lists
(of outputs, workspaces) and a table for each workspace. That approach turned
out to be complicated to use (snapping), understand and implement.
*Example*:
-------------------------------------------------
-status_command i3status --config ~/.i3status.conf
+bar {
+ status_command i3status --config ~/.i3status.conf
+}
-------------------------------------------------
=== Display mode
*Example*:
----------------
-mode hide
+bar {
+ mode hide
+}
----------------
=== Position
*Example*:
---------------------
-position top
+bar {
+ position top
+}
---------------------
=== Output(s)
handle all outputs. Restricting the outputs is useful for using different
options for different outputs by using multiple 'bar' blocks.
+To make a particular i3bar instance handle multiple outputs, specify the output
+directive multiple times.
+
*Syntax*:
---------------
output <output>
-------------------------------
# big monitor: everything
bar {
- output HDMI2
- status_command i3status
+ # The display is connected either via HDMI or via DisplayPort
+ output HDMI2
+ output DP2
+ status_command i3status
}
# laptop monitor: bright colors and i3status with less modules.
bar {
- output LVDS1
- status_command i3status --config ~/.i3status-small.conf
- colors {
- background #000000
- statusline #ffffff
- }
+ output LVDS1
+ status_command i3status --config ~/.i3status-small.conf
+ colors {
+ background #000000
+ statusline #ffffff
+ }
}
-------------------------------
*Example*:
-------------------------
# disable system tray
-tray_output none
+bar {
+ tray_output none
+}
# show tray icons on the big monitor
-tray_output HDMI2
+bar {
+ tray_output HDMI2
+}
-------------------------
=== Font
*Example*:
--------------------------------------------------------------
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+bar {
+ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+}
--------------------------------------------------------------
=== Workspace buttons
*Example*:
--------------------
-workspace_buttons no
+bar {
+ workspace_buttons no
+}
--------------------
=== Colors
*Example*:
--------------------------------------
-colors {
- background #000000
- statusline #ffffff
-
- focused_workspace #ffffff #285577
- active_workspace #ffffff #333333
- inactive_workspace #888888 #222222
- urgent_workspace #ffffff #900000
+bar {
+ colors {
+ background #000000
+ statusline #ffffff
+
+ focused_workspace #ffffff #285577
+ active_workspace #ffffff #333333
+ inactive_workspace #888888 #222222
+ urgent_workspace #ffffff #900000
+ }
}
--------------------------------------
bindsym mod+g exec gimp
# Start the terminal emulator urxvt which is not yet startup-notification-aware
-bindsym mod+enter exec --no-startup-id urxvt
+bindsym mod+Return exec --no-startup-id urxvt
------------------------------
The +--no-startup-id+ parameter disables startup-notification support for this
cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS}
echo "[i3-config-wizard] LEX $<"
- flex -i -o$(@:.o=.c) $<
+ $(FLEX) -i -o$(@:.o=.c) $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c)
cfgparse.y.o: cfgparse.y ${HEADERS}
echo "[i3-config-wizard] YACC $<"
- bison --debug --verbose -b $(basename $< .y) -d $<
+ $(BISON) --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
+ set_font(&font);
-#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font.height) + 2, text)
+#define txt(x, row, text) \
+ draw_text(text, strlen(text), false, pixmap, pixmap_gc,\
+ x, (row - 1) * font.height + 4, 300 - x * 2)
if (current_step == STEP_WELCOME) {
/* restore font color */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
txt(10, 2, "You have not configured i3 yet.");
txt(10, 3, "Do you want me to generate ~/.i3/config?");
txt(85, 7, "No, I will use the defaults");
/* green */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#00FF00") });
+ set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
txt(25, 5, "<Enter>");
/* red */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
txt(31, 7, "<ESC>");
}
if (current_step == STEP_GENERATE) {
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
txt(10, 2, "Please choose either:");
txt(85, 4, "Win as default modifier");
else txt(31, 4, "<Win>");
/* the selected modifier */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ bold_font.id });
+ set_font(&bold_font);
+ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
if (modifier == MOD_Mod4)
txt(31, 4, "<Win>");
else txt(31, 5, "<Alt>");
/* green */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_FONT,
- (uint32_t[]) { get_colorpixel("#00FF00"), font.id });
-
+ set_font(&font);
+ set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
txt(25, 9, "<Enter>");
/* red */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FF0000") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
txt(31, 10, "<ESC>");
}
extern xcb_window_t root;
-char *convert_ucs_to_utf8(char *input);
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
#endif
static int input_position;
static i3Font font;
static char *prompt;
-static int prompt_len;
+static size_t prompt_len;
static int limit;
xcb_window_t root;
xcb_connection_t *conn;
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
+
+ /* draw the text */
uint8_t *con = concat_strings(glyphs_ucs, input_position);
char *full_text = (char*)con;
if (prompt != NULL) {
memcpy(full_text, prompt, prompt_len * 2);
memcpy(full_text + (prompt_len * 2), con, input_position * 2);
}
- xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
- font.height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
+ if (input_position + prompt_len != 0)
+ draw_text(full_text, input_position + prompt_len, true, pixmap, pixmap_gc, 4, 4, 492);
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font.height + 8);
printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]);
/* convert it to UTF-8 */
- char *out = convert_ucs_to_utf8((char*)inp);
+ char *out = convert_ucs2_to_utf8((xcb_char2b_t*)inp, 1);
printf("converted to %s\n", out);
glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t));
if (glyphs_ucs[input_position] == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
memcpy(glyphs_ucs[input_position], inp, 3);
- glyphs_utf8[input_position] = strdup(out);
+ glyphs_utf8[input_position] = out;
input_position++;
if (input_position == limit)
sockfd = ipc_connect(socket_path);
if (prompt != NULL)
- prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+ prompt = (char*)convert_utf8_to_ucs2(prompt, &prompt_len);
int screens;
conn = xcb_connect(NULL, &screens);
symbols = xcb_key_symbols_alloc(conn);
font = load_font(pattern, true);
+ set_font(&font);
/* Open an input window */
win = xcb_generate_id(conn);
* this for us) */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
- /* Create graphics context */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-
/* Grab the keyboard to get all input */
xcb_flush(conn);
+++ /dev/null
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- * different contexts in X11.
- *
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
-
- char *buffer = scalloc(buffer_size);
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor == 0) {
- conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
- if (conversion_descriptor == 0)
- errx(EXIT_FAILURE, "Error opening the conversion context");
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- free(buffer);
- perror("Converting to UCS-2 failed");
- return NULL;
- }
-
- return buffer;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
-
- char *buffer = smalloc(buffer_size);
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor2 == 0) {
- conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
- if (conversion_descriptor2 == 0)
- errx(EXIT_FAILURE, "Error opening the conversion context");
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- free(buffer);
- if (real_strlen != NULL)
- *real_strlen = 0;
- return NULL;
- }
-
- if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
- return buffer;
-}
-
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
/* restore font color */
- uint32_t values[3];
- values[0] = color_text;
- values[1] = color_background;
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
- xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */,
- font.height + 2 + 4 /* Y = baseline of font */, prompt);
+ set_font_colors(pixmap_gc, color_text, color_background);
+ draw_text(prompt, strlen(prompt), false, pixmap, pixmap_gc,
+ 4 + 4, 4 + 4, rect.width - 4 - 4);
/* render close button */
int line_width = 4;
int w = 20;
int y = rect.width;
+ uint32_t values[3];
values[0] = color_button_background;
values[1] = line_width;
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
};
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
- values[0] = color_text;
- values[1] = color_button_background;
- values[2] = 1;
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values);
- xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */,
- font.height + 2 + 4 - 1/* Y = baseline of font */, "X");
+ values[0] = 1;
+ set_font_colors(pixmap_gc, color_text, color_button_background);
+ draw_text("X", 1, false, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4,
+ 4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4);
y -= w;
y -= 20;
values[0] = color_text;
values[1] = color_button_background;
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values);
- xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */,
- font.height + 2 + 3/* Y = baseline of font */, buttons[c].label);
+ set_font_colors(pixmap_gc, color_text, color_button_background);
+ draw_text(buttons[c].label, strlen(buttons[c].label), false, pixmap, pixmap_gc,
+ y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6);
y -= w;
}
}
font = load_font(pattern, true);
+ set_font(&font);
/* Open an input window */
win = xcb_generate_id(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + 8);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
- /* Create graphics context */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
-
/* Grab the keyboard to get all input */
xcb_flush(conn);
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
-
- /* Create graphics context */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ font.id });
break;
}
}
#
# Distributions/packagers can enhance this script with a
# distribution-specific mechanism to find the preferred pager.
-which $VISUAL >/dev/null && exec $VISUAL "$@"
-which $EDITOR >/dev/null && exec $EDITOR "$@"
+[ -n "$VISUAL" ] && which $VISUAL >/dev/null && exec $VISUAL "$@"
+[ -n "$EDITOR" ] && which $EDITOR >/dev/null && exec $EDITOR "$@"
# Hopefully one of these is installed (no flamewars about preference please!):
which nano >/dev/null && exec nano "$@"
#
# Distributions/packagers can enhance this script with a
# distribution-specific mechanism to find the preferred pager.
-which $PAGER >/dev/null && exec $PAGER "$@"
+[ -n "$PAGER" ] && which $PAGER >/dev/null && exec $PAGER "$@"
# Hopefully one of these is installed:
which most >/dev/null && exec most "$@"
# distribution-specific mechanism to find the preferred terminal emulator. On
# Debian, there is the x-terminal-emulator symlink for example.
# Please don't touch the first line, though:
-which $TERMINAL >/dev/null && exec $TERMINAL "$@"
+[ -n "$TERMINAL" ] && which $TERMINAL >/dev/null && exec $TERMINAL "$@"
# Hopefully one of these is installed:
which xterm >/dev/null && exec xterm "$@"
#include "workspaces.h"
#include "trayclients.h"
#include "xcb.h"
-#include "ucs2_to_utf8.h"
#include "config.h"
#include "libi3.h"
+++ /dev/null
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- * different contexts in X11.
- */
-#ifndef _UCS2_TO_UTF8
-#define _UCS2_TO_UTF8
-
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
-#endif
*/
void redraw_bars();
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length);
-
#endif
+++ /dev/null
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * ucs2_to_utf8.c: Converts between UCS-2 and UTF-8, both of which are used in
- * different contexts in X11.
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <err.h>
-#include <iconv.h>
-
-#include "libi3.h"
-
-static iconv_t conversion_descriptor = 0;
-static iconv_t conversion_descriptor2 = 0;
-
-/*
- * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
- * allocated, thus the caller has to free the output.
- *
- */
-char *convert_ucs_to_utf8(char *input) {
- size_t input_size = 2;
- /* UTF-8 may consume up to 4 byte */
- int buffer_size = 8;
-
- char *buffer = scalloc(buffer_size);
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor == 0) {
- conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
- if (conversion_descriptor == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- free(buffer);
- return NULL;
- }
-
- return buffer;
-}
-
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
-
- char *buffer = smalloc(buffer_size);
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor2 == 0) {
- conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
- if (conversion_descriptor2 == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- free(buffer);
- if (real_strlen != NULL)
- *real_strlen = 0;
- return NULL;
- }
-
- if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
- return buffer;
-}
params->workspaces_walk->name[len] = '\0';
/* Convert the name to ucs2, save its length in glyphs and calculate its rendered width */
- int ucs2_len;
+ size_t ucs2_len;
xcb_char2b_t *ucs2_name = (xcb_char2b_t*) convert_utf8_to_ucs2(params->workspaces_walk->name, &ucs2_len);
params->workspaces_walk->ucs2_name = ucs2_name;
params->workspaces_walk->name_glyphs = ucs2_len;
params->workspaces_walk->name_width =
- predict_text_extents(params->workspaces_walk->ucs2_name,
- params->workspaces_walk->name_glyphs);
+ predict_text_width((char *)params->workspaces_walk->ucs2_name,
+ params->workspaces_walk->name_glyphs, true);
DLOG("Got Workspace %s, name_width: %d, glyphs: %d\n",
params->workspaces_walk->name,
int screen;
xcb_screen_t *xcb_screen;
xcb_window_t xcb_root;
-xcb_font_t xcb_font;
-/* We need to cache some data to speed up text-width-prediction */
-xcb_query_font_reply_t *font_info;
-int font_height;
-xcb_charinfo_t *font_table;
+/* This is needed for integration with libi3 */
+xcb_connection_t *conn;
+
+/* The font we'll use */
+static i3Font font;
/* These are only relevant for XKB, which we only need for grabbing modifiers */
Display *xkb_dpy;
return 0;
}
-/*
- * Predicts the length of text based on cached data.
- * The string has to be encoded in ucs2 and glyph_len has to be the length
- * of the string (in glyphs).
- *
- */
-uint32_t predict_text_extents(xcb_char2b_t *text, uint32_t length) {
- /* If we don't have per-character data, return the maximum width */
- if (font_table == NULL) {
- return (font_info->max_bounds.character_width * length);
- }
-
- uint32_t width = 0;
- uint32_t i;
-
- for (i = 0; i < length; i++) {
- xcb_charinfo_t *info;
- int row = text[i].byte1;
- int col = text[i].byte2;
-
- if (row < font_info->min_byte1 || row > font_info->max_byte1 ||
- col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2) {
- continue;
- }
-
- /* Don't you ask me, how this one works… */
- info = &font_table[((row - font_info->min_byte1) *
- (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
- (col - font_info->min_char_or_byte2)];
-
- if (info->character_width != 0 ||
- (info->right_side_bearing |
- info->left_side_bearing |
- info->ascent |
- info->descent) != 0) {
- width += info->character_width;
- }
- }
-
- return width;
-}
-
-/*
- * Draws text given in UCS-2-encoding to a given drawable and position
- *
- */
-void draw_text(xcb_drawable_t drawable, xcb_gcontext_t ctx, int16_t x, int16_t y,
- xcb_char2b_t *text, uint32_t glyph_count) {
- int offset = 0;
- int16_t pos_x = x;
- int16_t font_ascent = font_info->font_ascent;
-
- while (glyph_count > 0) {
- uint8_t chunk_size = MIN(255, glyph_count);
- uint32_t chunk_width = predict_text_extents(text + offset, chunk_size);
-
- xcb_image_text_16(xcb_connection,
- chunk_size,
- drawable,
- ctx,
- pos_x, y + font_ascent,
- text + offset);
-
- offset += chunk_size;
- pos_x += chunk_width;
- glyph_count -= chunk_size;
- }
-}
-
/*
* Redraws the statusline to the buffer
*
*/
void refresh_statusline() {
- int glyph_count;
+ size_t glyph_count;
if (statusline == NULL) {
return;
}
- xcb_char2b_t *text = (xcb_char2b_t*) convert_utf8_to_ucs2(statusline, &glyph_count);
+ xcb_char2b_t *text = (xcb_char2b_t*)convert_utf8_to_ucs2(statusline, &glyph_count);
uint32_t old_statusline_width = statusline_width;
- statusline_width = predict_text_extents(text, glyph_count);
+ statusline_width = predict_text_width((char*)text, glyph_count, true);
/* If the statusline is bigger than our screen we need to make sure that
* the pixmap provides enough space, so re-allocate if the width grew */
if (statusline_width > xcb_screen->width_in_pixels &&
statusline_width > old_statusline_width)
realloc_sl_buffer();
- xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font_height };
+ xcb_rectangle_t rect = { 0, 0, xcb_screen->width_in_pixels, font.height };
xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
- draw_text(statusline_pm, statusline_ctx, 0, 0, text, glyph_count);
+ set_font_colors(statusline_ctx, colors.bar_fg, colors.bar_bg);
+ draw_text((char*)text, glyph_count, true, statusline_pm, statusline_ctx,
+ 0, 0, xcb_screen->width_in_pixels);
FREE(text);
}
values[0] = walk->rect.x;
if (config.position == POS_TOP)
values[1] = walk->rect.y;
- else values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+ else values[1] = walk->rect.y + walk->rect.h - font.height - 6;
values[2] = walk->rect.w;
- values[3] = font_height + 6;
+ values[3] = font.height + 6;
values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]);
cookie = xcb_configure_window_checked(xcb_connection,
clients++;
DLOG("Configuring tray window %08x to x=%d\n",
- trayclient->win, output->rect.w - (clients * (font_height + 2)));
- uint32_t x = output->rect.w - (clients * (font_height + 2));
+ trayclient->win, output->rect.w - (clients * (font.height + 2)));
+ uint32_t x = output->rect.w - (clients * (font.height + 2));
xcb_configure_window(xcb_connection,
trayclient->win,
XCB_CONFIG_WINDOW_X,
xcb_reparent_window(xcb_connection,
client,
output->bar,
- output->rect.w - font_height - 2,
+ output->rect.w - font.height - 2,
2);
/* We reconfigure the window to use a reasonable size. The systray
* specification explicitly says:
* should do their best to cope with any size effectively
*/
mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
- values[0] = font_height;
- values[1] = font_height;
+ values[0] = font.height;
+ values[1] = font.height;
xcb_configure_window(xcb_connection,
client,
mask,
continue;
xcb_rectangle_t rect;
- rect.x = output->rect.w - (clients * (font_height + 2));
+ rect.x = output->rect.w - (clients * (font.height + 2));
rect.y = 2;
- rect.width = font_height;
- rect.height = font_height;
+ rect.width = font.height;
+ rect.height = font.height;
DLOG("This is a tray window. x = %d\n", rect.x);
fake_configure_notify(xcb_connection, rect, event->window, 0);
ELOG("Cannot open display\n");
exit(EXIT_FAILURE);
}
+ conn = xcb_connection;
DLOG("Connected to xcb\n");
/* We have to request the atoms we need */
mask,
vals);
- mask |= XCB_GC_BACKGROUND;
- vals[0] = colors.bar_fg;
statusline_ctx = xcb_generate_id(xcb_connection);
xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection,
statusline_ctx,
xcb_root,
- mask,
- vals);
+ 0,
+ NULL);
statusline_pm = xcb_generate_id(xcb_connection);
xcb_void_cookie_t sl_pm_cookie = xcb_create_pixmap_checked(xcb_connection,
*
*/
void init_xcb_late(char *fontname) {
- if (fontname == NULL) {
- /* XXX: font fallback to 'misc' like i3 does it would be good. */
+ if (fontname == NULL)
fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
- }
- /* We load and allocate the font */
- xcb_font = xcb_generate_id(xcb_connection);
- xcb_void_cookie_t open_font_cookie;
- open_font_cookie = xcb_open_font_checked(xcb_connection,
- xcb_font,
- strlen(fontname),
- fontname);
-
- /* We need to save info about the font, because we need the font's height and
- * information about the width of characters */
- xcb_query_font_cookie_t query_font_cookie;
- query_font_cookie = xcb_query_font(xcb_connection,
- xcb_font);
-
- xcb_change_gc(xcb_connection,
- statusline_ctx,
- XCB_GC_FONT,
- (uint32_t[]){ xcb_font });
+ /* Load the font */
+ font = load_font(fontname, true);
+ set_font(&font);
+ DLOG("Calculated Font-height: %d\n", font.height);
xcb_flush(xcb_connection);
ev_io_start(main_loop, xkb_io);
XFlush(xkb_dpy);
}
-
- /* Now we save the font-infos */
- font_info = xcb_query_font_reply(xcb_connection,
- query_font_cookie,
- NULL);
-
- if (xcb_request_failed(open_font_cookie, "Could not open font")) {
- exit(EXIT_FAILURE);
- }
-
- font_height = font_info->font_ascent + font_info->font_descent;
-
- if (xcb_query_font_char_infos_length(font_info) == 0) {
- font_table = NULL;
- } else {
- font_table = xcb_query_font_char_infos(font_info);
- }
-
- DLOG("Calculated Font-height: %d\n", font_height);
}
/*
FREE(xcb_chk);
FREE(xcb_prep);
FREE(xcb_io);
- FREE(font_info);
}
/*
xcb_screen->height_in_pixels);
uint32_t mask = XCB_GC_FOREGROUND;
- uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font };
+ uint32_t vals[2] = { colors.bar_bg, colors.bar_bg };
xcb_free_gc(xcb_connection, statusline_clear);
statusline_clear = xcb_generate_id(xcb_connection);
xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection,
mask,
vals);
- mask |= XCB_GC_BACKGROUND | XCB_GC_FONT;
+ mask |= XCB_GC_BACKGROUND;
vals[0] = colors.bar_fg;
statusline_ctx = xcb_generate_id(xcb_connection);
xcb_free_gc(xcb_connection, statusline_ctx);
xcb_screen->root_depth,
walk->bar,
xcb_root,
- walk->rect.x, walk->rect.y + walk->rect.h - font_height - 6,
- walk->rect.w, font_height + 6,
+ walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6,
+ walk->rect.w, font.height + 6,
1,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
xcb_screen->root_visual,
case POS_NONE:
break;
case POS_TOP:
- strut_partial.top = font_height + 6;
+ strut_partial.top = font.height + 6;
strut_partial.top_start_x = walk->rect.x;
strut_partial.top_end_x = walk->rect.x + walk->rect.w;
break;
case POS_BOT:
- strut_partial.bottom = font_height + 6;
+ strut_partial.bottom = font.height + 6;
strut_partial.bottom_start_x = walk->rect.x;
strut_partial.bottom_end_x = walk->rect.x + walk->rect.w;
break;
/* We also want a graphics-context for the bars (it defines the properties
* with which we draw to them) */
walk->bargc = xcb_generate_id(xcb_connection);
- mask = XCB_GC_FONT;
- values[0] = xcb_font;
xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(xcb_connection,
walk->bargc,
walk->bar,
- mask,
- values);
+ 0,
+ NULL);
/* We finally map the bar (display it on screen), unless the modifier-switch is on */
xcb_void_cookie_t map_cookie;
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x;
- values[1] = walk->rect.y + walk->rect.h - font_height - 6;
+ values[1] = walk->rect.y + walk->rect.h - font.height - 6;
values[2] = walk->rect.w;
- values[3] = font_height + 6;
+ values[3] = font.height + 6;
values[4] = XCB_STACK_MODE_ABOVE;
DLOG("Destroying buffer for output %s", walk->name);
outputs_walk->bargc,
XCB_GC_FOREGROUND,
&color);
- xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font_height + 6 };
+ xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
/* We assume the tray icons are quadratic (we use the font
* *height* as *width* of the icons) because we configured them
* like this. */
- traypx += font_height + 2;
+ traypx += font.height + 2;
}
/* Add 2px of padding if there are any tray icons */
if (traypx > 0)
outputs_walk->bargc,
MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0,
MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3,
- MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height);
+ MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height);
}
if (config.disable_ws) {
outputs_walk->bargc,
mask,
vals);
- xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font_height + 4 };
+ xcb_rectangle_t rect = { i + 1, 1, ws_walk->name_width + 8, font.height + 4 };
xcb_poly_fill_rectangle(xcb_connection,
outputs_walk->buffer,
outputs_walk->bargc,
1,
&rect);
- xcb_change_gc(xcb_connection,
- outputs_walk->bargc,
- XCB_GC_FOREGROUND,
- &fg_color);
- xcb_image_text_16(xcb_connection,
- ws_walk->name_glyphs,
- outputs_walk->buffer,
- outputs_walk->bargc,
- i + 5, font_info->font_ascent + 2,
- ws_walk->ucs2_name);
+ set_font_colors(outputs_walk->bargc, fg_color, bg_color);
+ draw_text((char*)ws_walk->ucs2_name, ws_walk->name_glyphs, true,
+ outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width);
i += 10 + ws_walk->name_width;
}
Rect con_deco_rect;
uint32_t background;
bool con_is_leaf;
- xcb_font_t font;
};
/**
char *name_json;
/** The length of the name in glyphs (not bytes) */
- int name_len;
+ size_t name_len;
/** Whether the application used _NET_WM_NAME */
bool uses_net_wm_name;
*
*/
struct Font {
- /** The height of the font, built from font_ascent + font_descent */
- int height;
/** The xcb-id for the font */
xcb_font_t id;
+
+ /** Font information gathered from the server */
+ xcb_query_font_reply_t *info;
+
+ /** Font table for this font (may be NULL) */
+ xcb_charinfo_t *table;
+
+ /** The height of the font, built from font_ascent + font_descent */
+ int height;
};
/* Since this file also gets included by utilities which don’t use the i3 log
* the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
*
*/
-i3Font load_font(const char *pattern, bool fallback);
+i3Font load_font(const char *pattern, const bool fallback);
+
+/**
+ * Defines the font to be used for the forthcoming calls.
+ *
+ */
+void set_font(i3Font *font);
+
+/**
+ * Frees the resources taken by the current font.
+ *
+ */
+void free_font();
+
+/**
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs);
+
+/**
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen);
+
+/**
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background);
+
+/**
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+ xcb_gcontext_t gc, int x, int y, int max_width);
+
+/**
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2);
#endif
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie,
char *err_message);
-/**
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, a
- * buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen);
-
/**
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
*/
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
-/**
- * Calculate the width of the given text (16-bit characters, UCS) with given
- * real length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length);
-
/**
* Configures the given window to have the size/position specified by given rect
*
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 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 "libi3.h"
+
+extern xcb_connection_t *conn;
+static const i3Font *savedFont = NULL;
+
+/*
+ * Loads a font for usage, also getting its metrics. If fallback is true,
+ * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
+ *
+ */
+i3Font load_font(const char *pattern, const bool fallback) {
+ i3Font font;
+
+ /* Send all our requests first */
+ font.id = xcb_generate_id(conn);
+ xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.id,
+ strlen(pattern), pattern);
+ xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.id);
+
+ /* Check for errors. If errors, fall back to default font. */
+ xcb_generic_error_t *error;
+ error = xcb_request_check(conn, font_cookie);
+
+ /* If we fail to open font, fall back to 'fixed' */
+ if (fallback && error != NULL) {
+ ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
+ pattern, error->error_code);
+ pattern = "fixed";
+ font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+ info_cookie = xcb_query_font(conn, font.id);
+
+ /* Check if we managed to open 'fixed' */
+ error = xcb_request_check(conn, font_cookie);
+
+ /* Fall back to '-misc-*' if opening 'fixed' fails. */
+ if (error != NULL) {
+ ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
+ pattern = "-misc-*";
+ font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
+ info_cookie = xcb_query_font(conn, font.id);
+
+ if ((error = xcb_request_check(conn, font_cookie)) != NULL)
+ errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
+ "(fixed or -misc-*): X11 error %d", error->error_code);
+ }
+ }
+
+ /* Get information (height/name) for this font */
+ if (!(font.info = xcb_query_font_reply(conn, info_cookie, NULL)))
+ errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
+
+ /* Get the font table, if possible */
+ font.table = xcb_query_font_char_infos(font.info);
+
+ /* Calculate the font height */
+ font.height = font.info->font_ascent + font.info->font_descent;
+
+ return font;
+}
+
+/*
+ * Defines the font to be used for the forthcoming calls.
+ *
+ */
+void set_font(i3Font *font) {
+ savedFont = font;
+}
+
+/*
+ * Frees the resources taken by the current font.
+ *
+ */
+void free_font() {
+ /* Close the font and free the info */
+ xcb_close_font(conn, savedFont->id);
+ if (savedFont->info)
+ free(savedFont->info);
+}
+
+/*
+ * Defines the colors to be used for the forthcoming draw_text calls.
+ *
+ */
+void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) {
+ assert(savedFont != NULL);
+ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
+ uint32_t values[] = { foreground, background, savedFont->id };
+ xcb_change_gc(conn, gc, mask, values);
+}
+
+/*
+ * Draws text onto the specified X drawable (normally a pixmap) at the
+ * specified coordinates (from the top left corner of the leftmost, uppermost
+ * glyph) and using the provided gc. Text can be specified as UCS-2 or UTF-8.
+ *
+ */
+void draw_text(char *text, size_t text_len, bool is_ucs2, xcb_drawable_t drawable,
+ xcb_gcontext_t gc, int x, int y, int max_width) {
+ assert(savedFont != NULL);
+ assert(text_len != 0);
+
+ /* X11 coordinates for fonts start at the baseline */
+ int pos_y = y + savedFont->info->font_ascent;
+
+ /* As an optimization, check if we can bypass conversion */
+ if (!is_ucs2 && text_len <= 255) {
+ xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
+ return;
+ }
+
+ /* Convert the text into UCS-2 so we can do basic pointer math */
+ char *input = (is_ucs2 ? text : (char*)convert_utf8_to_ucs2(text, &text_len));
+
+ /* The X11 protocol limits text drawing to 255 chars, so we may need
+ * multiple calls */
+ int pos_x = x;
+ int offset = 0;
+ for (;;) {
+ /* Calculate the size of this chunk */
+ int chunk_size = (text_len > 255 ? 255 : text_len);
+ xcb_char2b_t *chunk = (xcb_char2b_t*)input + offset;
+
+ /* Draw it */
+ xcb_image_text_16(conn, chunk_size, drawable, gc, pos_x, pos_y, chunk);
+
+ /* Advance the offset and length of the text to draw */
+ offset += chunk_size;
+ text_len -= chunk_size;
+
+ /* Check if we're done */
+ if (text_len == 0)
+ break;
+
+ /* Advance pos_x based on the predicted text width */
+ pos_x += predict_text_width((char*)chunk, chunk_size, true);
+ }
+
+ /* If we had to convert, free the converted string */
+ if (!is_ucs2)
+ free(input);
+}
+
+static int xcb_query_text_width(xcb_char2b_t *text, size_t text_len) {
+ /* Make the user know we’re using the slow path, but only once. */
+ static bool first_invocation = true;
+ if (first_invocation) {
+ fprintf(stderr, "Using slow code path for text extents\n");
+ first_invocation = false;
+ }
+
+ /* Query the text width */
+ xcb_generic_error_t *error;
+ xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn,
+ savedFont->id, text_len, (xcb_char2b_t*)text);
+ xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn,
+ cookie, &error);
+ if (reply == NULL) {
+ /* We return a safe estimate because a rendering error is better than
+ * a crash. Plus, the user will see the error in his log. */
+ fprintf(stderr, "Could not get text extents (X error code %d)\n",
+ error->error_code);
+ return savedFont->info->max_bounds.character_width * text_len;
+ }
+
+ int width = reply->overall_width;
+ free(reply);
+ return width;
+}
+
+/*
+ * Predict the text width in pixels for the given text. Text can be specified
+ * as UCS-2 or UTF-8.
+ *
+ */
+int predict_text_width(char *text, size_t text_len, bool is_ucs2) {
+ /* Convert the text into UTF-16 so we can do basic pointer math */
+ xcb_char2b_t *input;
+ if (is_ucs2)
+ input = (xcb_char2b_t*)text;
+ else
+ input = convert_utf8_to_ucs2(text, &text_len);
+
+ int width;
+ if (savedFont->table == NULL) {
+ /* If we don't have a font table, fall back to querying the server */
+ width = xcb_query_text_width(input, text_len);
+ } else {
+ /* Save some pointers for convenience */
+ xcb_query_font_reply_t *font_info = savedFont->info;
+ xcb_charinfo_t *font_table = savedFont->table;
+
+ /* Calculate the width using the font table */
+ width = 0;
+ for (size_t i = 0; i < text_len; i++) {
+ xcb_charinfo_t *info;
+ int row = input[i].byte1;
+ int col = input[i].byte2;
+
+ if (row < font_info->min_byte1 ||
+ row > font_info->max_byte1 ||
+ col < font_info->min_char_or_byte2 ||
+ col > font_info->max_char_or_byte2)
+ continue;
+
+ /* Don't you ask me, how this one works… (Merovius) */
+ info = &font_table[((row - font_info->min_byte1) *
+ (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
+ (col - font_info->min_char_or_byte2)];
+
+ if (info->character_width != 0 ||
+ (info->right_side_bearing |
+ info->left_side_bearing |
+ info->ascent |
+ info->descent) != 0) {
+ width += info->character_width;
+ }
+ }
+ }
+
+ /* If we had to convert, free the converted string */
+ if (!is_ucs2)
+ free(input);
+
+ return width;
+}
+++ /dev/null
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
- *
- */
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdbool.h>
-#include <err.h>
-
-#include "libi3.h"
-
-extern xcb_connection_t *conn;
-
-/*
- * Loads a font for usage, also getting its height. If fallback is true,
- * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
- *
- */
-i3Font load_font(const char *pattern, bool fallback) {
- i3Font font;
- xcb_void_cookie_t font_cookie;
- xcb_list_fonts_with_info_cookie_t info_cookie;
- xcb_list_fonts_with_info_reply_t *info_reply;
- xcb_generic_error_t *error;
-
- /* Send all our requests first */
- font.id = xcb_generate_id(conn);
- font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- /* Check for errors. If errors, fall back to default font. */
- error = xcb_request_check(conn, font_cookie);
-
- /* If we fail to open font, fall back to 'fixed' */
- if (fallback && error != NULL) {
- ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
- pattern, error->error_code);
- pattern = "fixed";
- font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- /* Check if we managed to open 'fixed' */
- error = xcb_request_check(conn, font_cookie);
-
- /* Fall back to '-misc-*' if opening 'fixed' fails. */
- if (error != NULL) {
- ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
- pattern = "-misc-*";
- font_cookie = xcb_open_font_checked(conn, font.id, strlen(pattern), pattern);
- info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
-
- if ((error = xcb_request_check(conn, font_cookie)) != NULL)
- errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
- "(fixed or -misc-*): X11 error %d", error->error_code);
- }
- }
-
- /* Get information (height/name) for this font */
- if (!(info_reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL)))
- errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
-
- font.height = info_reply->font_ascent + info_reply->font_descent;
-
- free(info_reply);
-
- return font;
-}
--- /dev/null
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+#include <err.h>
+#include <errno.h>
+#include <iconv.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libi3.h"
+
+static iconv_t utf8_conversion_descriptor = (iconv_t)-1;
+static iconv_t ucs2_conversion_descriptor = (iconv_t)-1;
+
+/*
+ * Converts the given string to UTF-8 from UCS-2 big endian. The return value
+ * must be freed after use.
+ *
+ */
+char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) {
+ /* Allocate the output buffer (UTF-8 is at most 4 bytes per glyph) */
+ size_t buffer_size = num_glyphs * 4 * sizeof(char) + 1;
+ char *buffer = scalloc(buffer_size * sizeof(char));
+
+ /* We need to use an additional pointer, because iconv() modifies it */
+ char *output = buffer;
+ size_t output_size = buffer_size - 1;
+
+ if (utf8_conversion_descriptor == (iconv_t)-1) {
+ /* Get a new conversion descriptor */
+ utf8_conversion_descriptor = iconv_open("UTF-8", "UCS-2BE");
+ if (utf8_conversion_descriptor == (iconv_t)-1)
+ err(EXIT_FAILURE, "Error opening the conversion context");
+ } else {
+ /* Reset the existing conversion descriptor */
+ iconv(utf8_conversion_descriptor, NULL, NULL, NULL, NULL);
+ }
+
+ /* Do the conversion */
+ size_t input_len = num_glyphs * sizeof(xcb_char2b_t);
+ size_t rc = iconv(utf8_conversion_descriptor, (char**)&text,
+ &input_len, &output, &output_size);
+ if (rc == (size_t)-1) {
+ perror("Converting to UTF-8 failed");
+ free(buffer);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+/*
+ * Converts the given string to UCS-2 big endian for use with
+ * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
+ * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
+ * returned. It has to be freed when done.
+ *
+ */
+xcb_char2b_t *convert_utf8_to_ucs2(char *input, size_t *real_strlen) {
+ /* Calculate the input buffer size (UTF-8 is strlen-safe) */
+ size_t input_size = strlen(input);
+
+ /* Calculate the output buffer size and allocate the buffer */
+ size_t buffer_size = input_size * sizeof(xcb_char2b_t);
+ xcb_char2b_t *buffer = smalloc(buffer_size);
+
+ /* We need to use an additional pointer, because iconv() modifies it */
+ size_t output_size = buffer_size;
+ xcb_char2b_t *output = buffer;
+
+ if (ucs2_conversion_descriptor == (iconv_t)-1) {
+ /* Get a new conversion descriptor */
+ ucs2_conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
+ if (ucs2_conversion_descriptor == (iconv_t)-1)
+ err(EXIT_FAILURE, "Error opening the conversion context");
+ } else {
+ /* Reset the existing conversion descriptor */
+ iconv(ucs2_conversion_descriptor, NULL, NULL, NULL, NULL);
+ }
+
+ /* Do the conversion */
+ size_t rc = iconv(ucs2_conversion_descriptor, (char**)&input,
+ &input_size, (char**)&output, &output_size);
+ if (rc == (size_t)-1) {
+ perror("Converting to UCS-2 failed");
+ free(buffer);
+ if (real_strlen != NULL)
+ *real_strlen = 0;
+ return NULL;
+ }
+
+ /* Return the resulting string's length */
+ if (real_strlen != NULL)
+ *real_strlen = (buffer_size - output_size) / sizeof(xcb_char2b_t);
+
+ return buffer;
+}
TOKFONT STR
{
config.font = load_font($2, true);
+ set_font(&config.font);
printf("font %s\n", $2);
FREE(font_pattern);
font_pattern = $2;
TAILQ_FOREACH(ws, workspaces, workspaces)
workspace_set_name(ws, NULL);
#endif
+
+ /* Invalidate pixmap caches in case font or colors changed */
+ Con *con;
+ TAILQ_FOREACH(con, &all_cons, all_cons)
+ FREE(con->deco_render_params);
+
+ /* Get rid of the current font */
+ free_font();
}
SLIST_INIT(&modes);
if (config.font.id == 0) {
ELOG("You did not specify required configuration option \"font\"\n");
config.font = load_font("fixed", true);
+ set_font(&config.font);
+ }
+
+ /* Redraw the currently visible decorations on reload, so that
+ * the possibly new drawing parameters changed. */
+ if (reload) {
+ x_deco_recurse(croot);
+ xcb_flush(conn);
}
#if 0
ev_io_start(main_loop, ipc_io);
}
- /* Also handle the UNIX domain sockets passed via socket activation */
+ /* Also handle the UNIX domain sockets passed via socket activation. The
+ * parameter 1 means "remove the environment variables", we don’t want to
+ * pass these to child processes. */
int fds = sd_listen_fds(1);
if (fds < 0)
ELOG("socket activation: Error in sd_listen_fds\n");
}
/* find the height for the decorations */
- int deco_height = config.font.height + 5;
+ int deco_height = config.font.height + 4;
+ if (config.font.height & 0x01)
+ ++deco_height;
/* precalculate the sizes to be able to correct rounding errors */
int sizes[children];
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#FFFFFF") });
+ set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
- int text_len = strlen(crash_text[i]);
- char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
- xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
- 3 + (i + 1) * font_height /* Y = baseline of font */,
- (xcb_char2b_t*)full_text);
- free(full_text);
+ draw_text(crash_text[i], strlen(crash_text[i]), false, pixmap, pixmap_gc,
+ 8, 3 + (i - 1) * font_height, width - 16);
}
/* Copy the contents of the pixmap to the real window */
int height = 13 + (crash_text_num * config.font.height);
/* calculate width for longest text */
- int text_len = strlen(crash_text[crash_text_longest]);
- char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
- int font_width = predict_text_width(longest_text, text_len);
+ size_t text_len = strlen(crash_text[crash_text_longest]);
+ xcb_char2b_t *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
+ int font_width = predict_text_width((char *)longest_text, text_len, true);
int width = font_width + 20;
/* Open a popup window on each virtual screen */
xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
- /* Create graphics context */
- xcb_change_gc(conn, pixmap_gc, XCB_GC_FONT, (uint32_t[]){ config.font.id });
-
/* Grab the keyboard to get all input */
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
#include <sys/wait.h>
#include <stdarg.h>
-#include <iconv.h>
#if defined(__OpenBSD__)
#include <sys/cdefs.h>
#endif
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
-static iconv_t conversion_descriptor = 0;
-
int min(int a, int b) {
return (a < b ? a : b);
}
}
}
-/*
- * Converts the given string to UCS-2 big endian for use with
- * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
- * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
- * returned. It has to be freed when done.
- *
- */
-char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
- size_t input_size = strlen(input) + 1;
- /* UCS-2 consumes exactly two bytes for each glyph */
- int buffer_size = input_size * 2;
-
- char *buffer = smalloc(buffer_size);
- size_t output_size = buffer_size;
- /* We need to use an additional pointer, because iconv() modifies it */
- char *output = buffer;
-
- /* We convert the input into UCS-2 big endian */
- if (conversion_descriptor == 0) {
- conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
- if (conversion_descriptor == 0) {
- fprintf(stderr, "error opening the conversion context\n");
- exit(1);
- }
- }
-
- /* Get the conversion descriptor back to original state */
- iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
-
- /* Convert our text */
- int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
- if (rc == (size_t)-1) {
- perror("Converting to UCS-2 failed");
- FREE(buffer);
- if (real_strlen != NULL)
- *real_strlen = 0;
- return NULL;
- }
-
- if (real_strlen != NULL)
- *real_strlen = ((buffer_size - output_size) / 2) - 1;
-
- return buffer;
-}
-
/*
* This function resolves ~ in pathnames.
* It may resolve wildcards in the first part of the path, but if no match
return;
}
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
- int len;
- char *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
+ size_t len;
+ xcb_char2b_t *ucs2_name = convert_utf8_to_ucs2(new_name, &len);
if (ucs2_name == NULL) {
LOG("Could not convert _NET_WM_NAME to UCS-2, ignoring new hint\n");
FREE(new_name);
FREE(win->name_x);
FREE(win->name_json);
win->name_json = new_name;
- win->name_x = ucs2_name;
+ win->name_x = (char*)ucs2_name;
win->name_len = len;
win->name_x_changed = true;
LOG("_NET_WM_NAME changed to \"%s\"\n", win->name_json);
p->con_deco_rect = con->deco_rect;
p->background = config.client.background;
p->con_is_leaf = con_is_leaf(con);
- p->font = config.font.id;
if (con->deco_render_params != NULL &&
(con->window == NULL || !con->window->name_x_changed) &&
xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
/* 6: draw the title */
- uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
- uint32_t values[] = { p->color->text, p->color->background, config.font.id };
- xcb_change_gc(conn, parent->pm_gc, mask, values);
- int text_offset_y = config.font.height + (con->deco_rect.height - config.font.height) / 2 - 1;
+ set_font_colors(parent->pm_gc, p->color->text, p->color->background);
+ int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
struct Window *win = con->window;
if (win == NULL || win->name_x == NULL) {
/* this is a non-leaf container, we need to make up a good description */
// TODO: use a good description instead of just "another container"
- xcb_image_text_8(
- conn,
- strlen("another container"),
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2,
- con->deco_rect.y + text_offset_y,
- "another container"
- );
-
+ draw_text("another container", strlen("another container"), false,
+ parent->pixmap, parent->pm_gc,
+ con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
+ con->deco_rect.width - 2);
goto copy_pixmaps;
}
//DLOG("indent_level = %d, indent_mult = %d\n", indent_level, indent_mult);
int indent_px = (indent_level * 5) * indent_mult;
- if (win->uses_net_wm_name)
- xcb_image_text_16(
- conn,
- win->name_len,
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2 + indent_px,
- con->deco_rect.y + text_offset_y,
- (xcb_char2b_t*)win->name_x
- );
- else
- xcb_image_text_8(
- conn,
- win->name_len,
- parent->pixmap,
- parent->pm_gc,
- con->deco_rect.x + 2 + indent_px,
- con->deco_rect.y + text_offset_y,
- win->name_x
- );
+ draw_text(win->name_x, win->name_len, win->uses_net_wm_name,
+ parent->pixmap, parent->pm_gc,
+ con->deco_rect.x + 2 + indent_px, con->deco_rect.y + text_offset_y,
+ con->deco_rect.width - 2 - indent_px);
copy_pixmaps:
xcb_copy_area(conn, con->pixmap, con->frame, con->pm_gc, 0, 0, 0, 0, con->rect.width, con->rect.height);
xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
-/*
- * Query the width of the given text (16-bit characters, UCS) with given real
- * length (amount of glyphs) using the given font.
- *
- */
-int predict_text_width(char *text, int length) {
- xcb_query_text_extents_cookie_t cookie;
- xcb_query_text_extents_reply_t *reply;
- xcb_generic_error_t *error;
- int width;
-
- cookie = xcb_query_text_extents(conn, config.font.id, length, (xcb_char2b_t*)text);
- if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
- ELOG("Could not get text extents (X error code %d)\n",
- error->error_code);
- /* We return the rather safe guess of 7 pixels, because a
- * rendering error is better than a crash. Plus, the user will
- * see the error in his log. */
- return 7;
- }
-
- width = reply->overall_width;
- free(reply);
- return width;
-}
-
/*
* Configures the given window to have the size/position specified by given rect
*
'AnyEvent' => 0,
'AnyEvent::I3' => '0.09',
'X11::XCB' => '0.03',
- 'Test::Most' => 0,
- 'Test::Deep' => 0,
- 'EV' => 0,
- 'Inline' => 0,
+ 'Inline' => 0,
},
- # don't install any files from this directory
- PM => {},
+ PM => {}, # do not install any files from this directory
clean => {
- FILES => 'testsuite-* latest'
+ FILES => 'testsuite-* latest i3-cfg-for-*',
}
);
-# and don't run the tests while installing
-sub MY::test { }
+
+package MY;
+sub test { } # do not run the tests while installing
+
+# do not rename the Makefile
+sub clean {
+ my $section = shift->SUPER::clean(@_);
+ $section =~ s/^\t\Q$_\E\n$//m for
+ '- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)';
+ $section;
+}
use StatusLine;
# the following modules are not shipped with Perl
use AnyEvent;
+use AnyEvent::Util;
use AnyEvent::Handle;
use AnyEvent::I3 qw(:all);
use X11::XCB;
+# 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);
+
# We actually use AnyEvent to make sure it loads an event loop implementation.
# Afterwards, we overwrite SIGCHLD:
my $cv = AnyEvent->condvar;
my $coverage_testing = 0;
my $valgrind = 0;
+my $strace = 0;
my $help = 0;
# Number of tests to run in parallel. Important to know how many Xdummy
# instances we need to start (unless @displays are given). Defaults to
my $result = GetOptions(
"coverage-testing" => \$coverage_testing,
"valgrind" => \$valgrind,
+ "strace" => \$strace,
"display=s" => \@displays,
"parallel=i" => \$parallel,
"help|?" => \$help,
# We start tests concurrently: For each display, one test gets started. Every
# test starts another test after completing.
-take_job($_) for @wdisplays;
+for (@wdisplays) { $cv->begin; take_job($_) }
#
# Takes a test from the beginning of @testfiles and runs it.
sub take_job {
my ($display) = @_;
- my $test = shift @testfiles;
- return unless $test;
+ my $test = shift @testfiles
+ or return $cv->end;
my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/);
my $basename = basename($test);
display => $display,
configfile => $tmpfile,
outdir => $outdir,
- logpath => $logpath,
+ testname => $basename,
valgrind => $valgrind,
+ strace => $strace,
cv => $activate_cv
);
my $output;
open(my $spool, '>', \$output);
my $parser = TAP::Parser->new({
- exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" OUTDIR="$outdir" VALGRIND=$valgrind /usr/bin/perl -Ilib $test| ],
+ exec => [ 'sh', '-c', qq|DISPLAY=$display TESTNAME="$basename" OUTDIR="$outdir" VALGRIND=$valgrind STRACE=$strace /usr/bin/perl -Ilib $test| ],
spool => $spool,
merge => 1,
});
undef $_ for @watchers;
if (@done == $num) {
- $cv->send;
+ $cv->end;
} else {
take_job($display);
}
=item B<--valgrind>
Runs i3 under valgrind to find memory problems. The output will be available in
-C<latest/valgrind.log>.
+C<latest/valgrind-for-$test.log>.
+
+=item B<--strace>
+
+Runs i3 under strace to trace system calls. The output will be available in
+C<latest/strace-for-$test.log>.
=item B<--coverage-testing>
use warnings;
use IO::Socket::UNIX; # core
use Cwd qw(abs_path); # core
-use POSIX (); # core
+use POSIX qw(:fcntl_h); # core
use AnyEvent::Handle; # not core
+use AnyEvent::Util; # not core
use Exporter 'import';
use v5.10;
# remove the old unix socket
unlink($args{unix_socket_path});
- # pass all file descriptors up to three to the children.
- # we need to set this flag before opening the socket.
- open(my $fdtest, '<', '/dev/null');
- $^F = fileno($fdtest);
- close($fdtest);
my $socket = IO::Socket::UNIX->new(
Listen => 1,
Local => $args{unix_socket_path},
'..',
$ENV{PATH}
);
- # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
- # 3 (socket) to the child.
- $^F = 3;
+
+ # We are about to exec, but we did not modify $^F to include $socket
+ # when creating the socket (because the file descriptor could have a
+ # number != 3 which would lead to i3 leaking a file descriptor). This
+ # caused Perl to set the FD_CLOEXEC flag, which would close $socket on
+ # exec(), effectively *NOT* passing $socket to the new process.
+ # Therefore, we explicitly clear FD_CLOEXEC (the only flag right now)
+ # by setting the flags to 0.
+ POSIX::fcntl($socket, F_SETFD, 0) or die "Could not clear fd flags: $!";
# If the socket does not use file descriptor 3 by chance already, we
# close fd 3 and dup2() the socket to 3.
if (fileno($socket) != 3) {
POSIX::close(3);
POSIX::dup2(fileno($socket), 3);
+ POSIX::close(fileno($socket));
}
+ # Make sure no file descriptors are open. Strangely, I got an open file
+ # descriptor pointing to AnyEvent/Impl/EV.pm when testing.
+ AnyEvent::Util::close_all_fds_except(0, 1, 2, 3);
+
# Construct the command to launch i3. Use maximum debug level, disable
# the interactive signalhandler to make it crash immediately instead.
my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+ # For convenience:
+ my $outdir = $args{outdir};
+ my $test = $args{testname};
+
if ($args{valgrind}) {
$i3cmd =
- qq|valgrind -v --log-file="$args{outdir}/valgrind.log" | .
+ qq|valgrind -v --log-file="$outdir/valgrind-for-$test.log" | .
qq|--leak-check=full --track-origins=yes --num-callers=20 | .
qq|--tool=memcheck -- $i3cmd|;
}
- # Append to $args{logpath} instead of overwriting because i3 might be
+ my $logfile = "$outdir/i3-log-for-$test";
+ # Append to $logfile instead of overwriting because i3 might be
# run multiple times in one testcase.
- my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
+ my $cmd = "exec $i3cmd -c $args{configfile} >>$logfile 2>&1";
+
+ if ($args{strace}) {
+ my $out = "$outdir/strace-for-$test.log";
+
+ # We overwrite LISTEN_PID with the correct process ID to make
+ # socket activation work (LISTEN_PID has to match getpid(),
+ # otherwise the LISTEN_FDS will be treated as a left-over).
+ $cmd = qq|strace -fF -s2048 -v -o "$out" -- | .
+ 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+ }
# We need to use the shell due to using output redirections.
exec '/bin/sh', '-c', $cmd;
use X11::XCB::Window;
use X11::XCB qw(:all);
use AnyEvent::I3;
-use EV;
use List::Util qw(first);
use Time::HiRes qw(sleep);
use Cwd qw(abs_path);
+use Scalar::Util qw(blessed);
use SocketActivation;
use v5.10;
wait_for_event
wait_for_map
wait_for_unmap
+ $x
);
my $tester = Test::Builder->new();
my $_sync_window = undef;
my $tmp_socket_path = undef;
+our $x;
+
BEGIN {
my $window_count = 0;
sub counter_window {
sub import {
my $class = shift;
my $pkg = caller;
- eval "package $pkg;
-use Test::Most" . (@_ > 0 ? " qw(@_)" : "") . ";
+
+ my $test_more_args = @_ ? "qw(@_)" : "";
+ local $@;
+ eval << "__";
+package $pkg;
+use Test::More $test_more_args;
use Data::Dumper;
use AnyEvent::I3;
use Time::HiRes qw(sleep);
-use Test::Deep qw(eq_deeply cmp_deeply cmp_set cmp_bag cmp_methods useclass noclass set bag subbagof superbagof subsetof supersetof superhashof subhashof bool str arraylength Isa ignore methods regexprefonly regexpmatches num regexponly scalref reftype hashkeysonly blessed array re hash regexpref hash_each shallow array_each code arrayelementsonly arraylengthonly scalarrefonly listmethods any hashkeys isa);
-use v5.10;
-use strict;
-use warnings;
-";
+__
+ $tester->bail_out("$@") if $@;
+ feature->import(":5.10");
+ strict->import;
+ warnings->import;
+
+ $x ||= i3test::X11->new;
@_ = ($class);
goto \&Exporter::import;
}
# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY };
#
sub wait_for_event {
- my ($x, $timeout, $cb) = @_;
+ my ($timeout, $cb) = @_;
my $cv = AE::cv;
- my $prep = EV::prepare sub {
- $x->flush;
- };
+ $x->flush;
+
+ # unfortunately, there is no constant for this
+ my $ae_read = 0;
- my $check = EV::check sub {
+ my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
while (defined(my $event = $x->poll_for_event)) {
if ($cb->($event)) {
$cv->send(1);
}
};
- my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub {
- # do nothing, we only need this watcher so that EV picks up the events
- };
-
# Trigger timeout after $timeout seconds (can be fractional)
my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
my $result = $cv->recv;
undef $t;
+ undef $guard;
return $result;
}
# thin wrapper around wait_for_event which waits for MAP_NOTIFY
# make sure to include 'structure_notify' in the window’s event_mask attribute
sub wait_for_map {
- my ($x) = @_;
- wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
+ my ($win) = @_;
+ my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
+ wait_for_event 2, sub {
+ $_[0]->{response_type} == MAP_NOTIFY and $_[0]->{window} == $id
+ };
}
# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls
# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify
# event.
sub wait_for_unmap {
- my ($x) = @_;
- wait_for_event $x, 2, sub { $_[0]->{response_type} == UNMAP_NOTIFY };
- sync_with_i3($x);
+ my ($win) = @_;
+ # my $id = (blessed($win) && $win->isa('X11::XCB::Window')) ? $win->id : $win;
+ wait_for_event 2, sub {
+ $_[0]->{response_type} == UNMAP_NOTIFY # and $_[0]->{window} == $id
+ };
+ sync_with_i3();
}
#
#
# set dont_map to a true value to avoid mapping
#
+# if you want to change aspects of your window before it would be mapped,
+# set before_map to a coderef. $window gets passed as $_ and as first argument.
+#
+# if you set both dont_map and before_map, the coderef will be called nevertheless
+#
+#
# default values:
# class => WINDOW_CLASS_INPUT_OUTPUT
# rect => [ 0, 0, 30, 30 ]
# name => 'Window <n>'
#
sub open_window {
- my ($x, $args) = @_;
- my %args = ($args ? %$args : ());
+ my %args = @_ == 1 ? %{$_[0]} : @_;
my $dont_map = delete $args{dont_map};
+ my $before_map = delete $args{before_map};
$args{class} //= WINDOW_CLASS_INPUT_OUTPUT;
$args{rect} //= [ 0, 0, 30, 30 ];
my $window = $x->root->create_child(%args);
+ if ($before_map) {
+ # TODO: investigate why _create is not needed
+ $window->_create;
+ $before_map->($window) for $window;
+ }
+
return $window if $dont_map;
$window->map;
- wait_for_map($x);
- # We sync with i3 here to make sure $x->input_focus is updated.
- sync_with_i3($x);
+ wait_for_map($window);
return $window;
}
# Thin wrapper around open_window which sets window_type to
# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating.
sub open_floating_window {
- my ($x, $args) = @_;
- my %args = ($args ? %$args : ());
+ my %args = @_ == 1 ? %{$_[0]} : @_;
$args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY');
- return open_window($x, \%args);
+ return open_window(\%args);
}
sub open_empty_con {
# See also docs/testsuite for a long explanation
#
sub sync_with_i3 {
- my ($x) = @_;
-
# Since we need a (mapped) window for receiving a ClientMessage, we create
# one on the first call of sync_with_i3. It will be re-used in all
# subsequent calls.
if (!defined($_sync_window)) {
- $_sync_window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ),
+ $_sync_window = open_window(
+ rect => [ -15, -15, 10, 10 ],
override_redirect => 1,
- background_color => '#ff0000',
- event_mask => [ 'structure_notify' ],
);
-
- $_sync_window->map;
-
- wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY };
}
my $root = $x->get_root_window();
$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
# now wait until the reply is here
- return wait_for_event $x, 2, sub {
+ return wait_for_event 2, sub {
my ($event) = @_;
# TODO: const
return 0 unless $event->{response_type} == 161;
return $_cached_socket_path;
}
- my $x = X11::XCB::Connection->new;
my $atom = $x->atom(name => 'I3_SOCKET_PATH');
my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
my $reply = $x->get_property_reply($cookie->{sequence});
$tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-');
}
- my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1);
+ my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config-XXXXX', UNLINK => 1);
say $fh $config;
say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path;
close($fh);
display => $ENV{DISPLAY},
configfile => $tmpfile,
outdir => $ENV{OUTDIR},
- logpath => $ENV{LOGPATH},
+ testname => $ENV{TESTNAME},
valgrind => $ENV{VALGRIND},
+ strace => $ENV{STRACE},
cv => $cv,
);
return $pid;
}
+package i3test::X11;
+use parent 'X11::XCB::Connection';
+
+sub input_focus {
+ my $self = shift;
+ i3test::sync_with_i3();
+
+ return $self->SUPER::input_focus(@_);
+}
+
1
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => $original_rect,
- background_color => '#C0C0C0',
-);
-
+my $window = open_window(rect => $original_rect, dont_map => 1);
isa_ok($window, 'X11::XCB::Window');
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->map;
-
-sleep(0.25);
+wait_for_map $window;
my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
done_testing;
#
# checks if i3 supports I3_SYNC
#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
use i3test;
-my $x = X11::XCB::Connection->new;
-
-my $result = sync_with_i3($x);
+my $result = sync_with_i3;
ok($result, 'syncing was successful');
done_testing;
use i3test;
-my $x = X11::XCB::Connection->new;
-
fresh_workspace;
#####################################################################
#####################################################################
# Create a window so we can get a focus different from NULL
-my $window = open_window($x);
+my $window = open_window;
my $focus = $x->input_focus;
# Switch to another workspace
fresh_workspace;
-sync_with_i3($x);
+sync_with_i3;
my $new_focus = $x->input_focus;
isnt($focus, $new_focus, "Focus changed");
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
+my $window = open_window(
rect => $original_rect,
override_redirect => 1,
- background_color => '#C0C0C0',
+ dont_map => 1,
);
isa_ok($window, 'X11::XCB::Window');
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#C0C0C0',
- # replace the type with 'utility' as soon as the coercion works again in X11::XCB
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
- event_mask => [ 'structure_notify' ],
-);
+my $window = open_floating_window;
isa_ok($window, 'X11::XCB::Window');
-$window->map;
-
-wait_for_map $x;
-
my ($absolute, $top) = $window->rect;
ok($window->mapped, 'Window is mapped');
$window->unmap;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 80, 90],
- background_color => '#C0C0C0',
- window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
- event_mask => [ 'structure_notify' ],
-);
+$window = open_floating_window(rect => [ 1, 1, 80, 90 ]);
isa_ok($window, 'X11::XCB::Window');
-$window->map;
-
-wait_for_map $x;
-
($absolute, $top) = $window->rect;
cmp_ok($absolute->{width}, '==', 80, "i3 let the width at 80");
# at least the size of its initial geometry
#####################################################################
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 1, 1, 80, 90],
- background_color => '#C0C0C0',
- #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
- event_mask => [ 'structure_notify' ],
-);
+$window = open_window(rect => [ 1, 1, 80, 90 ]);
isa_ok($window, 'X11::XCB::Window');
-$window->map;
-
-wait_for_map $x;
-
cmd 'floating enable';
+sync_with_i3;
($absolute, $top) = $window->rect;
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
use List::Util qw(first);
my $i3 = i3(get_socket_path());
}
}
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
##################################
# map a window, then fullscreen it
##################################
my $original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
+my $window = open_window(
rect => $original_rect,
- background_color => '#C0C0C0',
- event_mask => [ 'structure_notify' ],
+ dont_map => 1,
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
-wait_for_map $x;
+wait_for_map $window;
# open another container to make the window get only half of the screen
cmd 'open';
my $new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
$original_rect = $new_rect;
$window->fullscreen(1);
-sync_with_i3($x);
+sync_with_i3;
$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned after fullscreen");
my $orect = $output->{rect};
my $wrect = $new_rect;
cmd 'open';
$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
+$window = open_window(
rect => $original_rect,
- background_color => 61440,
- event_mask => [ 'structure_notify' ],
+ dont_map => 1,
);
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->fullscreen(1);
$window->map;
-wait_for_map $x;
+wait_for_map $window;
$new_rect = $window->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned after fullscreen");
ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
$wrect = $new_rect;
###############################################################################
$original_rect = X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30);
-my $swindow = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
+my $swindow = open_window(
rect => $original_rect,
- background_color => '#C0C0C0',
- event_mask => [ 'structure_notify' ],
+ dont_map => 1,
);
$swindow->map;
-sync_with_i3($x);
+sync_with_i3;
ok(!$swindow->mapped, 'window not mapped while fullscreen window active');
$new_rect = $swindow->rect;
-ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
+ok(!eq_hash($new_rect, $original_rect), "Window got repositioned");
$swindow->fullscreen(1);
-sync_with_i3($x);
+sync_with_i3;
is(fullscreen_windows(), 1, 'amount of fullscreen windows');
$window->fullscreen(0);
-sync_with_i3($x);
+sync_with_i3;
is(fullscreen_windows(), 0, 'amount of fullscreen windows');
ok($swindow->mapped, 'window mapped after other fullscreen ended');
###########################################################################
$swindow->fullscreen(0);
-sync_with_i3($x);
+sync_with_i3;
is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling');
use i3test;
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
cmd 'layout default';
cmd 'split v';
-my $top = open_window($x);
-my $mid = open_window($x);
-my $bottom = open_window($x);
+my $top = open_window;
+my $mid = open_window;
+my $bottom = open_window;
#
# Returns the input focus after sending the given command to i3 via IPC
my $msg = shift;
cmd $msg;
- sync_with_i3 $x;
return $x->input_focus;
}
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after('focus up');
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
+use X11::XCB 'PROP_MODE_REPLACE';
use List::Util qw(first);
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
#####################################################################
# verify that there is no dock window yet
#####################################################################
my $primary = first { $_->primary } @{$screens};
# TODO: focus the primary screen before
-my $window = open_window($x, {
+my $window = open_window({
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
});
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40));
-sync_with_i3 $x;
+sync_with_i3;
@docked = get_dock_clients('top');
is(@docked, 1, 'one dock client found');
$window->destroy;
-wait_for_unmap $x;
+wait_for_unmap $window;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
# check if it gets placed on bottom (by coordinates)
#####################################################################
-$window = open_window($x, {
+$window = open_window({
rect => [ 0, 1000, 30, 30 ],
background_color => '#FF0000',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
});
-my $rect = $window->rect;
+$rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
is($rect->height, 30, 'height is unchanged');
$window->destroy;
-wait_for_unmap $x;
+wait_for_unmap $window;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
# check if it gets placed on bottom (by hint)
#####################################################################
-$window = open_window($x, {
+$window = open_window({
dont_map => 1,
rect => [ 0, 1000, 30, 30 ],
background_color => '#FF0000',
$window->map;
-wait_for_map $x;
+wait_for_map $window;
@docked = get_dock_clients('top');
is(@docked, 1, 'dock client on top');
$window->destroy;
-wait_for_unmap $x;
+wait_for_unmap $window;
@docked = get_dock_clients();
is(@docked, 0, 'no more dock clients');
-$window = open_window($x, {
+$window = open_window({
dont_map => 1,
rect => [ 0, 1000, 30, 30 ],
background_color => '#FF0000',
$window->_create();
# Add a _NET_WM_STRUT_PARTIAL hint
-my $atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
-my $atomtype = $x->atom(name => 'CARDINAL');
+$atomname = $x->atom(name => '_NET_WM_STRUT_PARTIAL');
+$atomtype = $x->atom(name => 'CARDINAL');
$x->change_property(
PROP_MODE_REPLACE,
$window->map;
-wait_for_map $x;
+wait_for_map $window;
@docked = get_dock_clients('bottom');
is(@docked, 1, 'dock client on bottom');
# regression test: transient dock client
#####################################################################
-$fwindow = open_window($x, {
+my $fwindow = open_window({
dont_map => 1,
background_color => '#FF0000',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
$fwindow->transient_for($window);
$fwindow->map;
-wait_for_map $x;
+wait_for_map $fwindow;
does_i3_live;
return $x->input_focus;
}
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("ml");
use i3test;
-my $x = X11::XCB::Connection->new;
-
fresh_workspace;
cmd 'split h';
-my $tiled_left = open_window($x);
-my $tiled_right = open_window($x);
+my $tiled_left = open_window;
+my $tiled_right = open_window;
# Get input focus before creating the floating window
my $focus = $x->input_focus;
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = open_floating_window($x);
+my $window = open_floating_window;
is($x->input_focus, $window->id, 'floating window focused');
$window->unmap;
-wait_for_unmap($x);
+wait_for_unmap $window;
is($x->input_focus, $focus, 'Focus correctly restored');
return $x->input_focus;
}
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("s");
use i3test;
use File::Temp;
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
cmd 'split h';
# Create two windows and make sure focus switching works
#####################################################################
-my $top = open_window($x);
-my $mid = open_window($x);
-my $bottom = open_window($x);
+my $top = open_window;
+my $mid = open_window;
+my $bottom = open_window;
#
# Returns the input focus after sending the given command to i3 via IPC
my $msg = shift;
cmd $msg;
- sync_with_i3($x);
return $x->input_focus;
}
-$focus = $x->input_focus;
+my $focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after('focus left');
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
fresh_workspace;
# Create a floating window and see if resizing works
#####################################################################
-my $window = open_floating_window($x);
+my $window = open_floating_window;
# See if configurerequests cause window movements (they should not)
my ($a, $t) = $window->rect;
$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height));
-sync_with_i3($x);
+sync_with_i3;
my ($na, $nt) = $window->rect;
is_deeply($na, $a, 'Rects are equal after configurerequest');
sub test_resize {
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100));
- sync_with_i3($x);
+ sync_with_i3;
my ($absolute, $top) = $window->rect;
$window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500));
- sync_with_i3($x);
+ sync_with_i3;
($absolute, $top) = $window->rect;
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
use List::Util qw(first);
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
cmd 'split v';
-my $top = open_window($x);
-my $bottom = open_window($x);
+my $top = open_window;
+my $bottom = open_window;
my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
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');
-sync_with_i3($x);
+sync_with_i3;
-@content = @{get_ws_content($tmp)};
+my @content = @{get_ws_content($tmp)};
@urgent = grep { $_->{urgent} } @content;
-$top_info = first { $_->{window} == $top->id } @content;
-$bottom_info = first { $_->{window} == $bottom->id } @content;
+my $top_info = first { $_->{window} == $top->id } @content;
+my $bottom_info = first { $_->{window} == $bottom->id } @content;
ok($top_info->{urgent}, 'top window is marked urgent');
ok(!$bottom_info->{urgent}, 'bottom window is not marked urgent');
is(@urgent, 0, 'no window got the urgent flag after focusing');
$top->add_hint('urgency');
-sync_with_i3($x);
+sync_with_i3;
@urgent = grep { $_->{urgent} } @{get_ws_content($tmp)};
is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint');
my $otmp = fresh_workspace;
$top->add_hint('urgency');
-sync_with_i3($x);
+sync_with_i3;
$ws = get_ws($tmp);
ok($ws->{urgent}, 'urgent flag set on workspace');
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
-}
-
-my $x = X11::XCB::Connection->new;
my $tmp = fresh_workspace;
# one of both (depending on your screen resolution) will be positioned wrong.
####################################################################################
-my $left = open_window($x, { name => 'Left' });
-my $right = open_window($x, { name => 'Right' });
+my $left = open_window({ name => 'Left' });
+my $right = open_window({ name => 'Right' });
my ($abs, $rgeom) = $right->rect;
-my $child = open_floating_window($x, {
+my $child = open_floating_window({
dont_map => 1,
name => 'Child window',
});
$child->client_leader($right);
$child->map;
-ok(wait_for_map($x), 'child window mapped');
+ok(wait_for_map($child), 'child window mapped');
my $cgeom;
($abs, $cgeom) = $child->rect;
cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
-my $child2 = open_floating_window($x, {
+my $child2 = open_floating_window({
dont_map => 1,
name => 'Child window 2',
});
$child2->client_leader($left);
$child2->map;
-ok(wait_for_map($x), 'second child window mapped');
+ok(wait_for_map($child2), 'second child window mapped');
($abs, $cgeom) = $child2->rect;
cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
# check wm_transient_for
-my $fwindow = open_window($x, { dont_map => 1 });
+my $fwindow = open_window({ dont_map => 1 });
$fwindow->transient_for($right);
$fwindow->map;
-ok(wait_for_map($x), 'transient window mapped');
+ok(wait_for_map($fwindow), 'transient window mapped');
my ($absolute, $top) = $fwindow->rect;
ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
# Create a parent window
#####################################################################
-my $window = open_window($x, { dont_map => 1, name => 'Parent window' });
+my $window = open_window({ dont_map => 1, name => 'Parent window' });
$window->map;
-ok(wait_for_map($x), 'parent window mapped');
+ok(wait_for_map($window), 'parent window mapped');
#########################################################################
# Switch to a different workspace and open a child window. It should be opened
#########################################################################
fresh_workspace;
-my $child = open_window($x, { dont_map => 1, name => 'Child window' });
+my $child = open_window({ dont_map => 1, name => 'Child window' });
$child->client_leader($window);
$child->map;
-ok(wait_for_map($x), 'child window mapped');
+ok(wait_for_map($child), 'child window mapped');
isnt($x->input_focus, $child->id, "Child window focused");
my $tree = $i3->get_tree->recv;
+# a unique value
+my $ignore = \"";
+
my $expected = {
fullscreen_mode => 0,
- nodes => ignore(),
+ nodes => $ignore,
window => undef,
name => 'root',
- orientation => ignore(),
+ orientation => $ignore,
type => 0,
- id => ignore(),
- rect => ignore(),
- window_rect => ignore(),
- geometry => ignore(),
- swallows => ignore(),
+ id => $ignore,
+ rect => $ignore,
+ window_rect => $ignore,
+ geometry => $ignore,
+ swallows => $ignore,
percent => undef,
layout => 'default',
- focus => ignore(),
+ focus => $ignore,
focused => JSON::XS::false,
urgent => JSON::XS::false,
border => 'normal',
- 'floating_nodes' => ignore(),
+ 'floating_nodes' => $ignore,
};
-cmp_deeply($tree, $expected, 'root node OK');
+# a shallow copy is sufficient, since we only ignore values at the root
+my $tree_copy = { %$tree };
+
+for (keys %$expected) {
+ my $val = $expected->{$_};
+
+ # delete unwanted keys, so we can use is_deeply()
+ if (ref($val) eq 'SCALAR' and $val == $ignore) {
+ delete $tree_copy->{$_};
+ delete $expected->{$_};
+ }
+}
+
+is_deeply($tree_copy, $expected, 'root node OK');
my @nodes = @{$tree->{nodes}};
is($ws->{num}, 3, 'workspace number is 3');
cmd "workspace 0: $tmp";
-my $ws = get_ws("0: $tmp");
+$ws = get_ws("0: $tmp");
ok(defined($ws), "workspace 0: $tmp was created");
is($ws->{num}, 0, 'workspace number is 0');
cmd "workspace aa: $tmp";
-my $ws = get_ws("aa: $tmp");
+$ws = get_ws("aa: $tmp");
ok(defined($ws), "workspace aa: $tmp was created");
is($ws->{num}, -1, 'workspace number is -1');
# Tests all kinds of matching methods
#
use i3test;
-use X11::XCB qw(:all);
+use X11::XCB qw(PROP_MODE_REPLACE);
my $tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
# Open a new window
-my $x = X11::XCB::Connection->new;
-my $window = open_window($x);
+my $window = open_window;
my $content = get_ws_content($tmp);
ok(@{$content} == 1, 'window mapped');
my $win = $content->[0];
my $id = $win->{id};
cmd qq|[con_id="$id"] kill|;
-wait_for_unmap $x;
+wait_for_unmap $window;
cmd 'nop checking if its gone';
$content = get_ws_content($tmp);
);
}
-my $left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$left->_create;
-set_wm_class($left->id, 'special', 'special');
-$left->name('left');
-$left->map;
-ok(wait_for_map($x), 'left window mapped');
-
-my $right = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$right->_create;
-set_wm_class($right->id, 'special', 'special');
-$right->name('right');
-$right->map;
-ok(wait_for_map($x), 'right window mapped');
+sub open_special {
+ my %args = @_;
+ my $wm_class = delete($args{wm_class}) || 'special';
+
+ return open_window(
+ %args,
+ before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+ );
+}
+
+my $left = open_special(name => 'left');
+ok($left->mapped, 'left window mapped');
+
+my $right = open_special(name => 'right');
+ok($right->mapped, 'right window mapped');
# two windows should be here
$content = get_ws_content($tmp);
cmd '[class="special" title="left"] kill';
-sync_with_i3($x);
+sync_with_i3;
$content = get_ws_content($tmp);
is(@{$content}, 1, 'one window still there');
$tmp = fresh_workspace;
-$left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$left->_create;
-set_wm_class($left->id, 'special7', 'special7');
-$left->name('left');
-$left->map;
-ok(wait_for_map($x), 'left window mapped');
+$left = open_special(name => 'left', wm_class => 'special7');
+ok($left->mapped, 'left window mapped');
# two windows should be here
$content = get_ws_content($tmp);
cmd '[class="^special[0-9]$"] kill';
-wait_for_unmap $x;
+wait_for_unmap $left;
$content = get_ws_content($tmp);
is(@{$content}, 0, 'window killed');
$tmp = fresh_workspace;
-$left = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$left->_create;
-set_wm_class($left->id, 'special7', 'special7');
-$left->name('ä 3');
-$left->map;
-ok(wait_for_map($x), 'left window mapped');
+$left = open_special(name => 'ä 3', wm_class => 'special7');
+ok($left->mapped, 'left window mapped');
# two windows should be here
$content = get_ws_content($tmp);
cmd '[title="^\w [3]$"] kill';
-wait_for_unmap $x;
+wait_for_unmap $left;
$content = get_ws_content($tmp);
is(@{$content}, 0, 'window killed');
# Tests splitting
#
use i3test;
-use X11::XCB qw(:all);
my $tmp = fresh_workspace;
# 4) move a container in a different direction so that we need to go up in tree
#
use i3test;
-use X11::XCB::Connection;
-my $x = X11::XCB::Connection->new;
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
######################################################################
$tmp = fresh_workspace;
-my $floatwin = open_floating_window($x);
+my $floatwin = open_floating_window;
my ($absolute_before, $top_before) = $floatwin->rect;
cmd 'move left';
my $floating = get_focused($tmp);
diag("focused floating: " . get_focused($tmp));
cmd 'mode toggle';
-# TODO: eliminate this race conditition
-sleep 1;
+sync_with_i3;
# kill old container
cmd qq|[con_id="$old"] focus|;
cmd qq|[con_id="$floating"] focus|;
is(get_focused($tmp), $floating, 'floating window focused');
-sleep 1;
+sync_with_i3;
cmd 'mode toggle';
does_i3_live;
cmd qq|[con_id="$first"] focus|;
cmd 'open';
-$content = get_ws_content($tmp);
+my $content = get_ws_content($tmp);
ok(@{$content} == 3, 'three containers opened');
is($content->[0]->{id}, $first, 'first container unmodified');
# Check if the focus is correctly restored after closing windows.
#
use i3test;
-use X11::XCB qw(:all);
use List::Util qw(first);
-my $x = X11::XCB::Connection->new;
-
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
##############################################################
cmd 'kill';
-# TODO: this testcase sometimes has different outcomes when the
-# sleep is missing. why?
-sleep 0.25;
+sync_with_i3;
+
($nodes, $focus) = get_ws_content($tmp);
is($nodes->[1]->{nodes}->[0]->{id}, $second, 'second container found');
ok($nodes->[1]->{nodes}->[0]->{focused}, 'second container focused');
$middle = open_empty_con($i3);
# XXX: the $right empty con will be filled with the x11 window we are creating afterwards
$right = open_empty_con($i3);
-my $win = open_window($x, { background_color => '#00ff00' });
+my $win = open_window({ background_color => '#00ff00' });
cmd qq|[con_id="$middle"] focus|;
$win->destroy;
-
-sleep 0.25;
+sync_with_i3;
is(get_focused($tmp), $middle, 'middle container focused');
cmd 'split v';
-($nodes, $focus) = get_ws_content($tmp);
+my ($nodes, $focus) = get_ws_content($tmp);
is($nodes->[0]->{focused}, 0, 'split container not focused');
my $split = $focus->[0];
cmd 'level down';
-my $second = open_empty_con($i3);
+$second = open_empty_con($i3);
isnt($first, $second, 'different container focused');
# focus the split container
cmd 'level up';
($nodes, $focus) = get_ws_content($tmp);
-my $split = $focus->[0];
+$split = $focus->[0];
cmd 'level down';
-my $second = open_empty_con($i3);
+$second = open_empty_con($i3);
isnt($first, $second, 'different container focused');
# We move the pointer out of our way to avoid a bug where the focus will
# be set to the window under the cursor
-my $x = X11::XCB::Connection->new;
$x->root->warp_pointer(0, 0);
my $tmp = get_unused_workspace();
#
use i3test;
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $win = open_window($x, { dont_map => 1 });
+my $win = open_window({ dont_map => 1 });
# XXX: we should check screen size. in screens with an AR of 2.0,
# this is not a good idea.
my $aspect = X11::XCB::Sizehints::Aspect->new;
$aspect->max_den(300);
$win->_create;
$win->map;
-wait_for_map $x;
+wait_for_map $win;
$win->hints->aspect($aspect);
$x->flush;
-sync_with_i3($x);
+sync_with_i3;
my $rect = $win->rect;
my $ar = $rect->width / $rect->height;
# vim:ts=4:sw=4:expandtab
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
my $tmp = fresh_workspace;
# 1: see if focus stays the same when toggling tiling/floating mode
#############################################################################
-my $first = open_window($x);
-my $second = open_window($x);
+my $first = open_window;
+my $second = open_window;
is($x->input_focus, $second->id, 'second window focused');
$tmp = fresh_workspace;
-$first = open_window($x); # window 2
-$second = open_window($x); # window 3
-my $third = open_window($x); # window 4
+$first = open_window; # window 2
+$second = open_window; # window 3
+my $third = open_window; # window 4
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
# now kill the third one (it's floating). focus should stay unchanged
cmd '[id="' . $third->id . '"] kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($third);
is($x->input_focus, $second->id, 'second con still focused after killing third');
$tmp = fresh_workspace;
-$first = open_window($x, { background_color => '#ff0000' }); # window 5
-$second = open_window($x, { background_color => '#00ff00' }); # window 6
-my $third = open_window($x, { background_color => '#0000ff' }); # window 7
+$first = open_window({ background_color => '#ff0000' }); # window 5
+$second = open_window({ background_color => '#00ff00' }); # window 6
+$third = open_window({ background_color => '#0000ff' }); # window 7
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
# now kill the second one. focus should fall back to the third one, which is
# also floating
cmd 'kill';
-
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($second);
is($x->input_focus, $third->id, 'third con focused');
cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($third);
is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
$tmp = fresh_workspace;
-$first = open_window($x, { background_color => '#ff0000' }); # window 5
+$first = open_window({ background_color => '#ff0000' }); # window 5
cmd 'split v';
cmd 'layout stacked';
-$second = open_window($x, { background_color => '#00ff00' }); # window 6
-$third = open_window($x, { background_color => '#0000ff' }); # window 7
+$second = open_window({ background_color => '#00ff00' }); # window 6
+$third = open_window({ background_color => '#0000ff' }); # window 7
is($x->input_focus, $third->id, 'last container focused');
cmd '[id="' . $second->id . '"] focus';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second con focused');
cmd 'floating enable';
-sync_with_i3($x);
+sync_with_i3;
# now kill the second one. focus should fall back to the third one, which is
# also floating
cmd 'kill';
-
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($second);
is($x->input_focus, $third->id, 'third con focused');
cmd 'kill';
-# TODO: wait for unmapnotify
-sync_with_i3($x);
+wait_for_unmap($third);
is($x->input_focus, $first->id, 'first con focused after killing all floating cons');
$tmp = fresh_workspace;
-$first = open_window($x, { background_color => '#ff0000' }); # window 8
-$second = open_window($x, { background_color => '#00ff00' }); # window 9
-
-sync_with_i3($x);
+$first = open_window({ background_color => '#ff0000' }); # window 8
+$second = open_window({ background_color => '#00ff00' }); # window 9
is($x->input_focus, $second->id, 'second container focused');
cmd 'focus tiling';
-sync_with_i3($x);
-
is($x->input_focus, $first->id, 'first (tiling) container focused');
cmd 'focus floating';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second (floating) container focused');
cmd 'focus floating';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second (floating) container still focused');
cmd 'focus mode_toggle';
-sync_with_i3($x);
-
is($x->input_focus, $first->id, 'first (tiling) container focused');
cmd 'focus mode_toggle';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second (floating) container focused');
#############################################################################
$tmp = fresh_workspace;
-$first = open_floating_window($x, { background_color => '#ff0000' });# window 10
-$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11
-$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12
-
-sync_with_i3($x);
+$first = open_floating_window({ background_color => '#ff0000' });# window 10
+$second = open_floating_window({ background_color => '#00ff00' }); # window 11
+$third = open_floating_window({ background_color => '#0000ff' }); # window 12
is($x->input_focus, $third->id, 'third container focused');
cmd 'focus left';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'second container focused');
cmd 'focus left';
-sync_with_i3($x);
-
is($x->input_focus, $first->id, 'first container focused');
cmd 'focus left';
-sync_with_i3($x);
-
is($x->input_focus, $third->id, 'focus wrapped to third container');
cmd 'focus right';
-sync_with_i3($x);
-
is($x->input_focus, $first->id, 'focus wrapped to first container');
cmd 'focus right';
-sync_with_i3($x);
-
is($x->input_focus, $second->id, 'focus on second container');
done_testing;
# Regression test: when only having a floating window on a workspace, it should not be deleted.
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
my $i3 = i3(get_socket_path());
ok(workspace_exists($tmp), "workspace $tmp exists");
-my $x = X11::XCB::Connection->new;
-
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = open_floating_window($x);
+my $window = open_floating_window;
ok($window->mapped, 'Window is mapped');
# switch to a different workspace, see if the window is still mapped?
# to a different workspace.
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
my $i3 = i3(get_socket_path());
# 1: open a floating window, get it mapped
#############################################################################
-my $x = X11::XCB::Connection->new;
-
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = open_floating_window($x);
+my $window = open_floating_window;
ok($window->mapped, 'Window is mapped');
# switch to a different workspace, see if the window is still mapped?
my $otmp = fresh_workspace;
-sync_with_i3($x);
+sync_with_i3;
ok(!$window->mapped, 'Window is not mapped after switching ws');
# if only a floating window is present on the workspace.
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
my $i3 = i3(get_socket_path());
# 1: open a floating window, get it mapped
#############################################################################
-my $x = X11::XCB::Connection->new;
-
# Create a floating window
-my $window = open_floating_window($x);
+my $window = open_floating_window;
ok($window->mapped, 'Window is mapped');
my $ws = get_ws($tmp);
is(@{$nodes}, 0, 'no tiling nodes');
# Create a tiling window
-my $twindow = open_window($x);
+my $twindow = open_window;
($nodes, $focus) = get_ws_content($tmp);
$tmp = fresh_workspace;
-my $first = open_window($x);
-my $second = open_window($x);
+my $first = open_window;
+my $second = open_window;
cmd 'layout stacked';
is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
# Create a floating window
-my $window = open_floating_window($x);
+$window = open_floating_window;
ok($window->mapped, 'Window is mapped');
$ws = get_ws($tmp);
is(@{$ws->{floating_nodes}}, 1, 'one floating nodes');
is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)');
-my $third = open_window($x);
+my $third = open_window;
$ws = get_ws($tmp);
# Check if numbered workspaces and named workspaces are sorted in the right way
# in get_workspaces IPC output (necessary for i3bar etc.).
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
sub check_order {
my ($msg) = @_;
my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
my @sorted = sort @nums;
- cmp_deeply(\@nums, \@sorted, $msg);
+ is_deeply(\@nums, \@sorted, $msg);
}
check_order('workspace order alright before testing');
cmd "workspace 93";
-open_window($x);
+open_window;
my @ws = @{$i3->get_workspaces->recv};
my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws;
check_order('workspace order alright after opening 93');
cmd "workspace 92";
-open_window($x);
+open_window;
check_order('workspace order alright after opening 92');
cmd "workspace 94";
-open_window($x);
+open_window;
check_order('workspace order alright after opening 94');
cmd "workspace 96";
-open_window($x);
+open_window;
check_order('workspace order alright after opening 96');
cmd "workspace foo";
-open_window($x);
+open_window;
check_order('workspace order alright after opening foo');
cmd "workspace 91";
-open_window($x);
+open_window;
check_order('workspace order alright after opening 91');
done_testing;
# Regression: Check if the focus stays the same when switching the layout
# bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
my $i3 = i3(get_socket_path());
-my $x = X11::XCB::Connection->new;
sub check_order {
my ($msg) = @_;
my @nums = map { $_->{num} } grep { defined($_->{num}) } @ws;
my @sorted = sort @nums;
- cmp_deeply(\@nums, \@sorted, $msg);
+ is_deeply(\@nums, \@sorted, $msg);
}
my $tmp = fresh_workspace;
-my $left = open_window($x);
-my $mid = open_window($x);
-my $right = open_window($x);
-
-sync_with_i3($x);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id);
# vim:ts=4:sw=4:expandtab
# Tests resizing tiling containers
use i3test;
-use X11::XCB qw(:all);
-
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
my $tmp = fresh_workspace;
cmd 'split v';
-my $top = open_window($x);
-my $bottom = open_window($x);
-
-sync_with_i3($x);
+my $top = open_window;
+my $bottom = open_window;
diag("top = " . $top->id . ", bottom = " . $bottom->id);
cmd 'split v';
-$top = open_window($x);
-$bottom = open_window($x);
+$top = open_window;
+$bottom = open_window;
cmd 'split h';
cmd 'layout stacked';
$tmp = fresh_workspace;
-$top = open_window($x);
+$top = open_window;
cmd 'floating enable';
my $tmp = fresh_workspace;
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
+my $first = open_window;
+my $second = open_window;
+
my ($nodes, $focus) = get_ws_content($tmp);
my $old_sum = sum map { $_->{rect}->{width} } @{$nodes};
-#cmd 'open';
+
cmd 'resize grow left 10 px or 25 ppt';
cmd 'split v';
-#cmd 'open';
-cmd 'exec /usr/bin/urxvt';
-sleep 0.5;
+
+sync_with_i3;
+
+my $third = open_window;
+
cmd 'mode toggle';
-sleep 0.5;
-cmd 'kill';
+sync_with_i3;
-sleep 0.5;
+cmd 'kill';
+sync_with_i3;
($nodes, $focus) = get_ws_content($tmp);
my $new_sum = sum map { $_->{rect}->{width} } @{$nodes};
#
# This testcase checks that the tree is properly flattened after moving.
#
-use X11::XCB qw(:all);
use i3test;
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
-my $left = open_window($x);
-my $mid = open_window($x);
-my $right = open_window($x);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
-cmd 'move before v';
-cmd 'move after h';
+cmd 'move up';
+cmd 'move right';
my $ws = get_ws($tmp);
is($ws->{orientation}, 'horizontal', 'workspace orientation is horizontal');
#!perl
# vim:ts=4:sw=4:expandtab
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
-my $left = open_window($x);
-my $mid = open_window($x);
+my $left = open_window;
+my $mid = open_window;
cmd 'split v';
-my $bottom = open_window($x);
+my $bottom = open_window;
my ($nodes, $focus) = get_ws_content($tmp);
#############################################################################
# Create a floating window
-my $window = open_floating_window($x);
+my $window = open_floating_window;
ok($window->mapped, 'Window is mapped');
($nodes, $focus) = get_ws_content($tmp);
cmd 'floating toggle';
-my ($nodes, $focus) = get_ws_content($tmp);
+($nodes, $focus) = get_ws_content($tmp);
is(@{$nodes->[1]->{nodes}}, 3, 'three windows in split con after floating toggle');
# Regression test for moving a con outside of a floating con when there are no
# tiling cons on a workspace
#
-use X11::XCB qw(:all);
-use Time::HiRes qw(sleep);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
+sub sync_cmd {
+ cmd @_;
+ sync_with_i3;
}
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
-my $left = open_window($x);
-my $mid = open_window($x);
-my $right = open_window($x);
+my $left = open_window;
+my $mid = open_window;
+my $right = open_window;
# go to workspace level
-cmd 'level up';
-sleep 0.25;
+sync_cmd 'level up';
# make it floating
-cmd 'mode toggle';
-sleep 0.25;
+sync_cmd 'mode toggle';
# move the con outside the floating con
-cmd 'move before v';
-sleep 0.25;
+sync_cmd 'move up';
does_i3_live;
# move another con outside
-cmd '[id="' . $mid->id . '"] focus';
-cmd 'move before v';
-sleep 0.25;
+sync_cmd '[id="' . $mid->id . '"] focus';
+sync_cmd 'move up';
does_i3_live;
# Regression test for correct focus behaviour when moving a floating con to
# another workspace.
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
# open a tiling window on the first workspace
-open_window($x);
-#sleep 0.25;
+open_window;
my $first = get_focused($tmp);
# on a different ws, open a floating window
my $otmp = fresh_workspace;
-open_window($x);
-#sleep 0.25;
+open_window;
my $float = get_focused($otmp);
cmd 'mode toggle';
-#sleep 0.25;
+sync_with_i3;
# move the floating con to first workspace
cmd "move workspace $tmp";
-#sleep 0.25;
+sync_with_i3;
# switch to the first ws and check focus
is(get_focused($tmp), $float, 'floating client correctly focused');
#
# Regression test for inplace restarting with dock clients
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
# open a dock client
-my $window = open_window($x, {
+my $window = open_window({
background_color => '#FF0000',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
});
$window->destroy;
-wait_for_unmap $x;
+wait_for_unmap $window;
@docked = get_dock_clients;
is(@docked, 0, 'no dock clients found');
# create a dock client with a 1px border
#####################################################################
-$window = open_window($x, {
+$window = open_window({
border => 1,
rect => [ 0, 0, 30, 20 ],
background_color => '#00FF00',
#
# Test if the requested width/height is set after making the window floating.
#
-use X11::XCB qw(:all);
use i3test;
my $tmp = fresh_workspace;
-my $x = X11::XCB::Connection->new;
-
# Create a floating window which is smaller than the minimum enforced size of i3
-my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] });
+my $window = open_window({ rect => [ 0, 0, 400, 150 ] });
my ($absolute, $top) = $window->rect;
cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height');
cmd 'floating toggle';
-sync_with_i3($x);
+sync_with_i3;
($absolute, $top) = $window->rect;
#
# Regression test for closing one of multiple dock clients
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
# open a dock client
#####################################################################
-my $first = open_window($x, {
+my $first = open_window({
background_color => '#FF0000',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
});
# Open a second dock client
#####################################################################
-my $second = open_window($x, {
+my $second = open_window({
background_color => '#FF0000',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
});
# Test to see if i3 combines the geometry of all children in a split container
# when setting the split container to floating
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
# open a window with 200x80
#####################################################################
-my $first = open_window($x, {
+my $first = open_window({
rect => [ 0, 0, 200, 80],
background_color => '#FF0000',
});
# Open a second window with 300x90
#####################################################################
-my $second = open_window($x, {
+my $second = open_window({
rect => [ 0, 0, 300, 90],
background_color => '#00FF00',
});
# Test if new containers get focused when there is a fullscreen container at
# the time of launching the new one.
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open the left window
#####################################################################
-my $left = open_window($x, { background_color => '#ff0000' });
+my $left = open_window({ background_color => '#ff0000' });
is($x->input_focus, $left->id, 'left window focused');
# Open the right window
#####################################################################
-my $right = open_window($x, { background_color => '#00ff00' });
+my $right = open_window({ background_color => '#00ff00' });
diag("right = " . $right->id);
# Open a third window
#####################################################################
-my $third = open_window($x, {
+my $third = open_window({
background_color => '#0000ff',
name => 'Third window',
dont_map => 1,
$third->map;
-sync_with_i3 $x;
+sync_with_i3;
diag("third = " . $third->id);
cmd "move workspace $tmp2";
# verify that the third window has the focus
-
-sync_with_i3($x);
-
is($x->input_focus, $third->id, 'third window focused');
done_testing;
#
# Regression test: level up should be a noop during fullscreen mode
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
-
my $tmp = fresh_workspace;
#####################################################################
# open a window, verify it’s not in fullscreen mode
#####################################################################
-my $win = open_window($x);
+my $win = open_window;
my $nodes = get_ws_content $tmp;
is(@$nodes, 1, 'exactly one client');
cmd 'nop making fullscreen';
cmd 'fullscreen';
-my $nodes = get_ws_content $tmp;
+$nodes = get_ws_content $tmp;
is($nodes->[0]->{fullscreen_mode}, 1, 'client fullscreen now');
#####################################################################
cmd 'level up';
cmd 'fullscreen';
-my $nodes = get_ws_content $tmp;
+$nodes = get_ws_content $tmp;
is($nodes->[0]->{fullscreen_mode}, 0, 'client not fullscreen any longer');
does_i3_live;
#
# Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
#
-use X11::XCB qw(:all);
use i3test;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
subtest 'Window without WM_TAKE_FOCUS', sub {
fresh_workspace;
- my $window = open_window($x);
+ my $window = open_window;
- ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage');
+ ok(!wait_for_event(1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage');
done_testing;
};
my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
- my $window = open_window($x, {
+ my $window = open_window({
dont_map => 1,
protocols => [ $take_focus ],
});
$window->map;
- ok(wait_for_event($x, 1, sub {
+ ok(wait_for_event(1, sub {
return 0 unless $_[0]->{response_type} == 161;
my ($data, $time) = unpack("L2", $_[0]->{data});
return ($data == $take_focus->id);
# restart.
# found in eb8ad348b28e243cba1972e802ca8ee636472fc9
#
-use X11::XCB qw(:all);
use List::Util qw(first);
use i3test;
-my $x = X11::XCB::Connection->new;
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
-my $window = open_window($x);
+my $window = open_window;
sub get_border_style {
my @content = @{get_ws_content($tmp)};
# Regression test for setting the urgent hint on dock clients.
# found in 4be3178d4d360c2996217d811e61161c84d25898
#
-use X11::XCB qw(:all);
use i3test;
-BEGIN {
- use_ok('X11::XCB::Window');
-}
-
-my $x = X11::XCB::Connection->new;
my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;
# open a dock client
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30],
- background_color => '#FF0000',
+my $window = open_window(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
);
-$window->map;
-
-sleep 0.25;
-
#####################################################################
# check that we can find it in the layout tree at the expected position
#####################################################################
$window->add_hint('urgency');
-sync_with_i3($x);
+sync_with_i3;
does_i3_live;
# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when
# unmapped.
#
-use X11::XCB qw(:all);
use i3test;
+use X11::XCB qw(ICCCM_WM_STATE_NORMAL ICCCM_WM_STATE_WITHDRAWN);
-my $x = X11::XCB::Connection->new;
-
-my $window = open_window($x);
-
-sync_with_i3($x);
+my $window = open_window;
is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal');
$window->unmap;
-wait_for_unmap $x;
+wait_for_unmap $window;
is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn');
#
use i3test;
-my $x = X11::XCB::Connection->new;
-
sub two_windows {
my $tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
- my $first = open_window($x);
- my $second = open_window($x);
-
- sync_with_i3 $x;
+ my $first = open_window;
+ my $second = open_window;
is($x->input_focus, $second->id, 'second window focused');
ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
# 'kill window'
##############################################################
-my $tmp = two_windows;
+$tmp = two_windows;
cmd 'kill window';
# and check if both are gone
##############################################################
-my $tmp = two_windows;
+$tmp = two_windows;
cmd 'kill client';
# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3
#
#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
use i3test;
-
-my $x = X11::XCB::Connection->new;
+use X11::XCB qw(PROP_MODE_REPLACE);
##############################################################
# 1: test the following directive:
my $tmp = fresh_workspace;
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->name('Border window');
-$window->map;
-wait_for_map $x;
+my $window = open_window(name => 'Border window');
my @content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border');
$window->unmap;
-wait_for_unmap $x;
+wait_for_unmap $window;
-my @content = @{get_ws_content($tmp)};
+@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
diag('content = '. Dumper(\@content));
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
# TODO: move this to X11::XCB::Window
sub set_wm_class {
);
}
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('Borderless window');
-$window->map;
-wait_for_map $x;
+$window = open_window(
+ name => 'Borderless window',
+ before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
+);
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
$window->unmap;
-wait_for_unmap $x;
+wait_for_unmap $window;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->name('special title');
-$window->map;
-wait_for_map $x;
+$window = open_window(name => 'special title');
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border');
$window->name('special borderless title');
-sync_with_i3 $x;
+sync_with_i3;
@content = @{get_ws_content($tmp)};
is($content[0]->{border}, 'none', 'no border');
$window->name('special title');
-sync_with_i3 $x;
+sync_with_i3;
cmd 'border normal';
is($content[0]->{border}, 'normal', 'border reset to normal');
$window->name('special borderless title');
-sync_with_i3 $x;
+sync_with_i3;
@content = @{get_ws_content($tmp)};
is($content[0]->{border}, 'normal', 'still normal border');
$window->unmap;
-wait_for_unmap $x;
+wait_for_unmap $window;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no more nodes');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->name('special mark title');
-$window->map;
-wait_for_map $x;
+$window = open_window(name => 'special mark title');
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
-my $other = open_window($x);
+my $other = open_window;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 2, 'two nodes');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
+$window = open_window(
+ name => 'usethis',
+ before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') },
);
-$window->_create;
-
-set_wm_class($window->id, 'borderless', 'borderless');
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
-
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
cmd 'kill';
-wait_for_unmap $x;
+wait_for_unmap $window;
$window->destroy;
+# give i3 a chance to delete the window from its tree
+sync_with_i3;
+
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 0, 'no nodes on this workspace now');
set_wm_class($window->id, 'borderless', 'borderless');
$window->name('notthis');
$window->map;
-wait_for_map $x;
+wait_for_map $window;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
+$window = open_window(
+ name => 'usethis',
+ before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
+);
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
+$window = open_window(
+ name => 'usethis',
+ before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
);
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
-
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
+$window = open_window(
+ name => 'usethis',
+ before_map => sub { set_wm_class($_->id, 'bar', 'foo') },
);
-$window->_create;
-
-set_wm_class($window->id, 'bar', 'foo');
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
-
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-
-my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
-my $atomtype = $x->atom(name => 'STRING');
-$x->change_property(
- PROP_MODE_REPLACE,
- $window->id,
- $atomname->id,
- $atomtype->id,
- 8,
- length("i3test") + 1,
- "i3test\x00"
+$window = open_window(
+ name => 'usethis',
+ before_map => sub {
+ my ($window) = @_;
+ my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+ my $atomtype = $x->atom(name => 'STRING');
+ $x->change_property(
+ PROP_MODE_REPLACE,
+ $window->id,
+ $atomname->id,
+ $atomtype->id,
+ 8,
+ length("i3test") + 1,
+ "i3test\x00"
+ );
+ },
);
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
-
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'none', 'no border (window_role)');
$tmp = fresh_workspace;
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#00ff00',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-
-$window->name('usethis');
-$window->map;
-wait_for_map $x;
+$window = open_window(name => 'usethis');
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
is($content[0]->{border}, 'normal', 'normal border (window_role 2)');
-$atomname = $x->atom(name => 'WM_WINDOW_ROLE');
-$atomtype = $x->atom(name => 'STRING');
+my $atomname = $x->atom(name => 'WM_WINDOW_ROLE');
+my $atomtype = $x->atom(name => 'STRING');
$x->change_property(
PROP_MODE_REPLACE,
$window->id,
$x->flush;
-sync_with_i3 $x;
+sync_with_i3;
@content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
# Tests if assignments work
#
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
+use X11::XCB qw(PROP_MODE_REPLACE);
# TODO: move to X11::XCB
sub set_wm_class {
);
}
+sub open_special {
+ my %args = @_;
+ my $wm_class = delete($args{wm_class}) || 'special';
+ $args{name} //= 'special window';
+
+ return open_window(
+ %args,
+ before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+ );
+}
#####################################################################
# start a window and see that it does not get assigned with an empty config
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-wait_for_map $x;
+my $window = open_special;
ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace');
my $workspaces = get_workspace_names;
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-wait_for_map $x;
+$window = open_special;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
ok("targetws" ~~ @{get_workspace_names()}, 'targetws does not exist yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
# We use sync_with_i3 instead of wait_for_map here because i3 will not actually
# map the window -- it will be assigned to a different workspace and will only
# be mapped once you switch to that workspace
-sync_with_i3 $x;
+$window = open_special(dont_map => 1);
+$window->map;
+sync_with_i3;
ok(@{get_ws_content($tmp)} == 0, 'still no containers');
ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws');
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
+$workspaces = get_workspace_names;
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-wait_for_map $x;
+$window = open_special;
my $content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $workspaces = get_workspace_names;
+$workspaces = get_workspace_names;
ok(!("targetws" ~~ @{$workspaces}), 'targetws does not exist yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
- event_mask => [ 'structure_notify' ],
-);
-
-$window->_create;
-set_wm_class($window->id, 'SPEcial', 'SPEcial');
-$window->name('special window');
-$window->map;
-wait_for_map $x;
+$window = open_special(wm_class => 'SPEcial');
-my $content = get_ws($tmp);
+$content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 1, 'one floating con');
# syntax
is(@docked, 1, 'one dock client yet');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
+$window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
- event_mask => [ 'structure_notify' ],
);
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-wait_for_map $x;
-
-my $content = get_ws($tmp);
+$content = get_ws($tmp);
ok(@{$content->{nodes}} == 0, 'no tiling cons');
ok(@{$content->{floating_nodes}} == 0, 'one floating con');
@docked = get_dock_clients;
exit_gracefully($pid);
-sleep 0.25;
-
done_testing;
#
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
#####################################################################
# 1: check that with an empty config, cons are place next to each
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $first = open_window($x);
-my $second = open_window($x);
-
-sync_with_i3($x);
+my $first = open_window;
+my $second = open_window;
is($x->input_focus, $second->id, 'second window focused');
-ok(@{get_ws_content($tmp)} == 2, 'two containers opened');
+my @content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_window($x);
-$second = open_window($x);
-
-sync_with_i3($x);
+$first = open_window;
+$second = open_window;
is($x->input_focus, $second->id, 'second window focused');
-my @content = @{get_ws_content($tmp)};
+@content = @{get_ws_content($tmp)};
ok(@content == 1, 'one con at workspace level');
is($content[0]->{layout}, 'stacked', 'layout stacked');
#####################################################################
cmd 'focus parent';
-my $right_top = open_window($x);
-my $right_bot = open_window($x);
+my $right_top = open_window;
+my $right_bot = open_window;
@content = @{get_ws_content($tmp)};
is(@content, 2, 'two cons at workspace level after focus parent');
# Verifies that i3 survives inplace restarts with fullscreen containers
#
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
fresh_workspace;
-open_window($x);
-open_window($x);
+open_window;
+open_window;
cmd 'layout stacking';
-sleep 1;
+sync_with_i3;
cmd 'fullscreen';
-sleep 1;
+sync_with_i3;
cmd 'restart';
sleep 1;
# Tests if the 'force_focus_wrapping' config directive works correctly.
#
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
#####################################################################
# 1: test the wrapping behaviour without force_focus_wrapping
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $first = open_window($x);
-my $second = open_window($x);
+my $first = open_window;
+my $second = open_window;
cmd 'layout tabbed';
cmd 'focus parent';
-my $third = open_window($x);
+my $third = open_window;
is($x->input_focus, $third->id, 'third window focused');
cmd 'focus left';
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_window($x);
-$second = open_window($x);
+$first = open_window;
+$second = open_window;
cmd 'layout tabbed';
cmd 'focus parent';
-$third = open_window($x);
-
-sync_with_i3($x);
+$third = open_window;
is($x->input_focus, $third->id, 'third window focused');
#
# checks if i3 starts up on workspace '1' or the first configured named workspace
#
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
use i3test;
-my $x = X11::XCB::Connection->new;
-
##############################################################
# 1: i3 should start with workspace '1'
##############################################################
my $pid = launch_with_config($config);
my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
+is_deeply(\@names, [ '1' ], 'i3 starts on workspace 1 without any configuration');
exit_gracefully($pid);
$pid = launch_with_config($config);
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
exit_gracefully($pid);
$pid = launch_with_config($config);
-my @names = @{get_workspace_names()};
-cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
+@names = @{get_workspace_names()};
+is_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar');
exit_gracefully($pid);
my $tmp = fresh_workspace;
my $marks = get_marks();
-cmp_deeply($marks, [], 'no marks set so far');
+is_deeply($marks, [], 'no marks set so far');
##############################################################
# 2: check that setting a mark is reflected in the get_marks reply
cmd 'open';
cmd 'mark foo';
-cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set');
+is_deeply(get_marks(), [ 'foo' ], 'mark foo set');
##############################################################
# 3: check that the mark is gone after killing the container
cmd 'kill';
-cmp_deeply(get_marks(), [ ], 'mark gone');
+is_deeply(get_marks(), [ ], 'mark gone');
##############################################################
# 4: check that duplicate marks are included twice in the get_marks reply
cmd 'open';
cmd 'mark bar';
-cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
+is_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice');
done_testing;
# assigned to an invisible workspace
#
use i3test;
-use X11::XCB qw(:all);
-use X11::XCB::Connection;
-use v5.10;
-
-my $x = X11::XCB::Connection->new;
+use X11::XCB qw(PROP_MODE_REPLACE);
# TODO: move to X11::XCB
sub set_wm_class {
);
}
+sub open_special {
+ my %args = @_;
+ my $wm_class = delete($args{wm_class}) || 'special';
+ $args{name} //= 'special window';
+
+ return open_window(
+ %args,
+ before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) },
+ );
+}
#####################################################################
# start a window and see that it does not get assigned with an empty config
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
ok(get_ws($tmp)->{focused}, 'current workspace focused');
-my $window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
-);
-
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
+my $window = open_special;
ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
# the same test, but with a floating window
#####################################################################
-$window = $x->root->create_child(
- class => WINDOW_CLASS_INPUT_OUTPUT,
- rect => [ 0, 0, 30, 30 ],
- background_color => '#0000ff',
+$window = open_special(
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
-$window->_create;
-set_wm_class($window->id, 'special', 'special');
-$window->name('special window');
-$window->map;
-sleep 0.25;
-
-
ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace');
ok(@{get_ws_content('targetws')} == 1, 'special window on targetws');
ok(get_ws($tmp)->{focused}, 'current workspace still focused');
use i3test;
-my $x = X11::XCB::Connection->new;
-
#####################################################################
# 1: check that new windows start with 'normal' border unless configured
# otherwise
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-my $first = open_window($x);
+my $first = open_window;
my @content = @{get_ws_content($tmp)};
ok(@content == 1, 'one container opened');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_window($x);
+$first = open_window;
@content = @{get_ws_content($tmp)};
ok(@content == 1, 'one container opened');
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_floating_window($x);
+$first = open_floating_window;
my $wscontent = get_ws($tmp);
my @floating = @{$wscontent->{floating_nodes}};
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
-$first = open_floating_window($x);
+$first = open_floating_window;
$wscontent = get_ws($tmp);
@floating = @{$wscontent->{floating_nodes}};
use POSIX qw(mkfifo);
use File::Temp qw(:POSIX);
-my $x = X11::XCB::Connection->new;
use ExtUtils::PkgConfig;
# setup dependency on libstartup-notification using pkg-config
is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
-my $win = open_window($x, { dont_map => 1 });
+my $win = open_window({ dont_map => 1 });
mark_window($win->id);
$win->map;
# We don’t use wait_for_map because the window will not get mapped -- it is on
# a different workspace.
# We sync with i3 here to make sure $x->input_focus is updated.
-sync_with_i3($x);
+sync_with_i3;
is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
# same thing, but with _NET_STARTUP_ID set on the leader
######################################################################
-my $leader = open_window($x, { dont_map => 1 });
+my $leader = open_window({ dont_map => 1 });
mark_window($leader->id);
-$win = open_window($x, { dont_map => 1, client_leader => $leader });
+$win = open_window({ dont_map => 1, client_leader => $leader });
$win->map;
-sync_with_i3($x);
+sync_with_i3;
is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
######################################################################
complete_startup();
-sync_with_i3($x);
+sync_with_i3;
-my $otherwin = open_window($x);
+my $otherwin = open_window;
is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
######################################################################
#
use i3test;
-use X11::XCB::Connection;
-
-my $x = X11::XCB::Connection->new;
my $config = <<EOT;
# i3 config file (v4)
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Verifies that i3 does not leak any file descriptors in 'exec'.
+#
+use i3test;
+use POSIX qw(mkfifo);
+use File::Temp qw(:POSIX tempfile);
+
+my $i3 = i3(get_socket_path());
+
+my $tmp = tmpnam();
+mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
+my ($outfh, $outname) = tempfile('/tmp/i3-ls-output.XXXXXX', UNLINK => 1);
+
+cmd qq|exec ls -l /proc/self/fd >$outname && echo done >$tmp|;
+
+open(my $fh, '<', $tmp);
+# Block on the FIFO, this will return exactly when the command is done.
+<$fh>;
+close($fh);
+unlink($tmp);
+
+# Get the ls /proc/self/fd output
+my $output;
+{
+ local $/;
+ $output = <$outfh>;
+}
+close($outfh);
+
+# Split lines, keep only those which are symlinks.
+my @lines = grep { /->/ } split("\n", $output);
+
+my %fds = map { /([0-9]+) -> (.+)$/; ($1, $2) } @lines;
+
+# Filter out 0, 1, 2 (stdin, stdout, stderr).
+delete $fds{0};
+delete $fds{1};
+delete $fds{2};
+
+# Filter out the fd which is caused by ls calling readdir().
+for my $fd (keys %fds) {
+ delete $fds{$fd} if $fds{$fd} =~ m,^/proc/\d+/fd$,;
+}
+
+is(scalar keys %fds, 0, 'No file descriptors leaked');
+
+done_testing;
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Regression test: Changing border style should not have an impact on the size
+# (geometry) of the child window. See ticket http://bugs.i3wm.org/561
+# Wrong behaviour manifested itself up to (including) commit
+# d805d1bbeaf89e11f67c981f94c9f55bbb4b89d9
+#
+use i3test;
+use Data::Dumper;
+
+fresh_workspace;
+
+my $win = open_floating_window(rect => [10, 10, 200, 100]);
+
+my $geometry = $win->rect;
+is($geometry->{width}, 200, 'width correct');
+is($geometry->{height}, 100, 'height correct');
+
+cmd 'border 1pixel';
+
+$geometry = $win->rect;
+is($geometry->{width}, 200, 'width correct');
+is($geometry->{height}, 100, 'height correct');
+
+done_testing;