From: Michael Stapelberg Date: Sat, 17 Dec 2011 18:48:14 +0000 (+0000) Subject: Merge branch 'master' into next X-Git-Tag: 4.2~181 X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=commitdiff_plain;h=6f8b2845863e9071d7d630b270729d46423c1eb5;hp=7daac508077749bf0bd76dcedf3e836407c34a74 Merge branch 'master' into next --- diff --git a/.gitignore b/.gitignore index cad6ad9a..b641592b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ loglevels.tmp *.gcno testcases/testsuite-* testcases/latest +testcases/Makefile *.output *.tab.* *.yy.c diff --git a/Makefile b/Makefile index c0797839..8a34ecfc 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ else UNUSED:=$(shell $(MAKE) loglevels.h) endif -SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar +SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar i3-dump-log # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} @@ -55,23 +55,23 @@ loglevels.h: 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) @@ -100,7 +100,7 @@ dist: distclean [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} - cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} + cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs # Pre-generate documentation diff --git a/common.mk b/common.mk index a9d16618..537d4dda 100644 --- a/common.mk +++ b/common.mk @@ -2,6 +2,8 @@ UNAME=$(shell uname) DEBUG=1 COVERAGE=0 INSTALL=install +FLEX=flex +BISON=bison ifndef PREFIX PREFIX=/usr endif @@ -66,6 +68,7 @@ CPPFLAGS += -DPCRE_HAS_UCP=1 endif LIBS += -lm +LIBS += -lrt LIBS += -L $(TOPDIR)/libi3 -li3 LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) diff --git a/debian/control b/debian/control index e3786cfb..1119d69d 100644 --- a/debian/control +++ b/debian/control @@ -10,8 +10,8 @@ Homepage: http://i3wm.org/ 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). diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index e5896855..8d2662aa 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -17,3 +17,9 @@ docs/keyboard-layer2.png 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 diff --git a/debian/patches/use-x-terminal-emulator.patch b/debian/patches/use-x-terminal-emulator.patch index fd515c16..28e9200d 100644 --- a/debian/patches/use-x-terminal-emulator.patch +++ b/debian/patches/use-x-terminal-emulator.patch @@ -11,7 +11,7 @@ -# 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 "$@" diff --git a/docs/hacking-howto b/docs/hacking-howto index 9a7ec9d4..d0f5ac3a 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -63,57 +63,22 @@ to a specific file type, a window manager should not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to easily create the layout you need at the moment. -=== The layout table +=== The layout tree -********************************************************************************* -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* :). -********************************************************************************* - -///////////////////////////////////////////////////////////////////////////////// -To accomplish flexible layouts, we decided to simply use a table. The table -grows and shrinks as you need it. Each cell holds a container which then holds -windows (see picture below). You can use different layouts for each container -(default layout and stacking layout). +The data structure which i3 uses to keep track of your windows is a tree. Every +node in the tree is a container (type +Con+). Some containers represent actual +windows (every container with a +window != NULL+), some represent split +containers and a few have special purposes: they represent workspaces, outputs +(like VGA1, LVDS1, …) or the X11 root window. So, when you open a terminal and immediately open another one, they reside in -the same container, in default layout. The layout table has exactly one column, -one row and therefore one cell. When you move one of the terminals to the -right, the table needs to grow. It will be expanded to two columns and one row. -This enables you to have different layouts for each container. The table then -looks like this: - -[width="15%",cols="^,^"] -|======== -| T1 | T2 -|======== - -When moving terminal 2 to the bottom, the table will be expanded again. - -[width="15%",cols="^,^"] -|======== -| T1 | -| | T2 -|======== +the same split container, which uses the default layout. In case of an empty +workspace, the split container we are talking about is the workspace. -You can really think of the layout table like a traditional HTML table, if -you’ve ever designed one. Especially col- and rowspan work similarly. Below, -you see an example of colspan=2 for the first container (which has T1 as -window). - -[width="15%",cols="^asciidoc"] -|======== -| T1 -| -[cols="^,^",frame="none"] -!======== -! T2 ! T3 -!======== -|======== - -Furthermore, you can freely resize table cells. -///////////////////////////////////////////////////////////////////////////////// +To get an impression of how different layouts are represented, just play around +and look at the data structures -- they are exposed as a JSON hash. See +http://i3wm.org/docs/ipc.html#_get_tree_reply for documentation on that and an +example. == Files @@ -160,7 +125,7 @@ src/debug.c:: 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). @@ -243,17 +208,13 @@ Legacy support for Xinerama. See +src/randr.c+ for the preferred API. == 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] ///////////////////////////////////////////////////////////////////////////////// @@ -261,7 +222,7 @@ 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) @@ -269,68 +230,57 @@ So, the hierarchy is: The data type is +Con+, in all cases. -=== Virtual screens +=== X11 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. +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. + +=== 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* :). -********************************************************************************* +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. -///////////////////////////////////////////////////////////////////////////////// - -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). +Split containers (and X11 window containers, which are a subtype of split +containers) can have different border styles. -///////////////////////////////////////////////////////////////////////////////// +=== X11 window container -=== 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 @@ -421,8 +371,9 @@ After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see 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, @@ -484,64 +435,218 @@ src/layout.c, function resize_client(). == 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 @@ -780,3 +885,73 @@ git format-patch origin ----------------------- 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. diff --git a/docs/ipc b/docs/ipc index fc46590e..e73ca6f8 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -October 2011 +December 2011 This document describes how to interface with i3 from a separate process. This is useful for example to remote-control i3 (to write test cases for example) or @@ -46,7 +46,7 @@ Currently implemented message types are the following: COMMAND (0):: The payload of the message is a command for i3 (like the commands you can bind to keys in the configuration file) and will be executed - directly after receiving it. There is no reply to this message. + directly after receiving it. GET_WORKSPACES (1):: Gets the current workspaces. The reply will be a JSON-encoded list of workspaces (see the reply section). @@ -68,6 +68,10 @@ GET_BAR_CONFIG (6):: Gets the configuration (as JSON map) of the workspace bar with the given ID. If no ID is provided, an array with all configured bar IDs is returned instead. +GET_LOG_MARKERS (7):: + Gets the SHM log markers for the current position, the last wrap, the + SHM segment name and segment size. This is necessary for tools like + i3-dump-log which want to display the SHM log. So, a typical message could look like this: -------------------------------------------------- @@ -111,18 +115,20 @@ The following reply types are implemented: COMMAND (0):: Confirmation/Error code for the COMMAND message. -GET_WORKSPACES (1):: +WORKSPACES (1):: Reply to the GET_WORKSPACES message. SUBSCRIBE (2):: Confirmation/Error code for the SUBSCRIBE message. -GET_OUTPUTS (3):: +OUTPUTS (3):: Reply to the GET_OUTPUTS message. -GET_TREE (4):: +TREE (4):: Reply to the GET_TREE message. -GET_MARKS (5):: +MARKS (5):: Reply to the GET_MARKS message. -GET_BAR_CONFIG (6):: +BAR_CONFIG (6):: Reply to the GET_BAR_CONFIG message. +LOG_MARKERS (7):: + Reply to the GET_LOG_MARKERS message. === COMMAND reply @@ -134,7 +140,7 @@ property is +success (bool)+, but this will be expanded in future versions. { "success": true } ------------------- -=== GET_WORKSPACES reply +=== WORKSPACES reply The reply consists of a serialized list of workspaces. Each workspace has the following properties: @@ -248,7 +254,7 @@ rect (map):: ] ------------------- -=== GET_TREE reply +=== TREE reply The reply consists of a serialized tree. Each node in the tree (representing one container) has at least the properties listed below. While the nodes might @@ -431,7 +437,7 @@ JSON dump: } ------------------------ -=== GET_MARKS reply +=== MARKS reply The reply consists of a single array of strings for each container that has a mark. The order of that array is undefined. If more than one container has the @@ -440,7 +446,7 @@ contents are not unique). If no window has a mark the response will be the empty array []. -=== GET_BAR_CONFIG reply +=== BAR_CONFIG reply This can be used by third-party workspace bars (especially i3bar, but others are free to implement compatible alternatives) to get the +bar+ block @@ -524,6 +530,40 @@ urgent_workspace_text/urgent_workspace_bar:: } -------------- +=== LOG_MARKERS reply + +Gets the SHM log markers for the current position, the last wrap, the +SHM segment name and segment size. This is necessary for tools like +i3-dump-log which want to display the SHM log. + +The reply is a JSON map with the following entries: + +shmname (string):: + The name of the SHM segment, will be of the format +/i3-log-+. +size (integer):: + The size (in bytes) of the SHM segment. If this is 0, SHM logging is + disabled. +offset_next_write (integer):: + The offset in the SHM segment at which the next write will happen. + Tools should start printing lines from here, since the bytes following + this offset are the oldest log lines. However, the first line might be + garbled, so it makes sense to skip all bytes until the first \0. +offset_last_wrap (integer):: + The offset in the SHM segment at which the last wrap occured. i3 only + stores entire messages in the SHM log, so it might waste a few bytes at + the end to be more efficient. Tools should not print content after the + offset_last_wrap. + +*Example*: +----------------------------- +{ + "offset_next_write":132839, + "offset_last_wrap":26214400, + "shmname":"/i3-log-3392", + "size":26214400 +} +----------------------------- + == Events [[events]] diff --git a/docs/userguide b/docs/userguide index 3d78d16f..26c12b77 100644 --- a/docs/userguide +++ b/docs/userguide @@ -174,7 +174,7 @@ Floating windows are always on top of tiling windows. 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. @@ -796,6 +796,28 @@ bar { } --------------------------- +=== i3bar command + +By default i3 will just pass +i3bar+ and let your shell handle the execution, +searching your +$PATH+ for a correct version. +If you have a different +i3bar+ somewhere or the binary is not in your +$PATH+ you can +tell i3 what to execute. + +The specified command will be passed to +sh -c+, so you can use globbing and +have to have correct quoting etc. + +*Syntax*: +---------------------- +i3bar_command command +---------------------- + +*Example*: +------------------------------------------------- +bar { + i3bar_command /home/user/bin/i3bar +} +------------------------------------------------- + === Statusline command i3bar can run a program and display every line of its +stdout+ output on the @@ -812,7 +834,9 @@ status_command command *Example*: ------------------------------------------------- -status_command i3status --config ~/.i3status.conf +bar { + status_command i3status --config ~/.i3status.conf +} ------------------------------------------------- === Display mode @@ -834,7 +858,9 @@ mode *Example*: ---------------- -mode hide +bar { + mode hide +} ---------------- === Position @@ -850,7 +876,9 @@ position *Example*: --------------------- -position top +bar { + position top +} --------------------- === Output(s) @@ -859,6 +887,9 @@ You can restrict i3bar to one or more outputs (monitors). The default is to 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 @@ -868,18 +899,20 @@ 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 + } } ------------------------------- @@ -899,10 +932,14 @@ tray_output *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 @@ -917,7 +954,9 @@ 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 @@ -934,7 +973,9 @@ workspace_buttons *Example*: -------------------- -workspace_buttons no +bar { + workspace_buttons no +} -------------------- === Colors @@ -974,14 +1015,16 @@ 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 + } } -------------------------------------- @@ -1063,7 +1106,7 @@ exec [--no-startup-id] command 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 diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 27d5bf54..75d4684f 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -26,12 +26,12 @@ $(TOPDIR)/libi3/%.a: $(TOPDIR)/libi3/*.c 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) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index cdce0653..84a7f77e 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -112,13 +112,15 @@ static int handle_expose() { 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?"); @@ -126,16 +128,16 @@ static int handle_expose() { 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, ""); /* 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, ""); } 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"); @@ -150,19 +152,19 @@ static int handle_expose() { else txt(31, 4, ""); /* 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, ""); else txt(31, 5, ""); /* 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, ""); /* 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, ""); } diff --git a/i3-dump-log/Makefile b/i3-dump-log/Makefile new file mode 100644 index 00000000..18076e51 --- /dev/null +++ b/i3-dump-log/Makefile @@ -0,0 +1,32 @@ +# Default value so one can compile i3-dump-log standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +CFLAGS += -I$(TOPDIR)/include + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "[i3-dump-log] CC $<" + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +all: i3-dump-log + +i3-dump-log: ${FILES} + echo "[i3-dump-log] LINK i3-dump-log" + $(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS) + +install: all + echo "[i3-dump-log] INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-dump-log diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c new file mode 100644 index 00000000..68cbdb46 --- /dev/null +++ b/i3-dump-log/main.c @@ -0,0 +1,160 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + * i3-dump-log/main.c: Dumps the i3 SHM log to stdout. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libi3.h" +#include + +int main(int argc, char *argv[]) { + char *socket_path = getenv("I3SOCK"); + int o, option_index = 0; + int message_type = I3_IPC_MESSAGE_TYPE_GET_LOG_MARKERS; + bool verbose = false; + + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"version", no_argument, 0, 'v'}, + {"verbose", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "s:vVh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + if (o == 's') { + if (socket_path != NULL) + free(socket_path); + socket_path = sstrdup(optarg); + } else if (o == 'v') { + printf("i3-dump-log " I3_VERSION "\n"); + return 0; + } else if (o == 'V') { + verbose = true; + } else if (o == 'h') { + printf("i3-dump-log " I3_VERSION "\n"); + printf("i3-dump-log [-s ]\n"); + return 0; + } + } + + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + /* Fall back to the default socket path */ + if (socket_path == NULL) + socket_path = sstrdup("/tmp/i3-ipc.sock"); + + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + if (ipc_send_message(sockfd, 0, message_type, NULL) == -1) + err(EXIT_FAILURE, "IPC: write()"); + + uint32_t reply_length; + uint8_t *reply; + int ret; + if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + exit(1); + } + char *buffer = NULL; + sasprintf(&buffer, "%.*s", reply_length, reply); + /* The reply will look like this: + * {"offset_next_write":1729,"offset_last_wrap":1996,"size":2048,"shmname":"/i3-log-399"} + * IMO, it’s not worth linking a JSON parser in just for this. If the + * structure changes in the future, this decision needs to be re-evaluated + * :). */ + int offset_next_write, offset_last_wrap, logbuffer_size; + char *next_write_str = strstr(buffer, "offset_next_write"), + *last_wrap_str = strstr(buffer, "offset_last_wrap"), + *size_str = strstr(buffer, "size"), + *shmname = strstr(buffer, "shmname"); + if (!next_write_str || + !last_wrap_str || + !size_str || + !shmname || + sscanf(next_write_str, "offset_next_write\":%d", &offset_next_write) != 1 || + sscanf(last_wrap_str, "offset_last_wrap\":%d", &offset_last_wrap) != 1 || + sscanf(size_str, "size\":%d", &logbuffer_size) != 1) + errx(EXIT_FAILURE, "invalid IPC reply: %s\n", buffer); + + shmname += strlen("shmname\":\""); + char *quote = strchr(shmname, '"'); + if (!quote) + errx(EXIT_FAILURE, "invalid IPC reply: %s\n", buffer); + *quote = '\0'; + + if (verbose) + printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n", + offset_next_write, offset_last_wrap, logbuffer_size, shmname); + + if (*shmname == '\0') + errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3."); + + int logbuffer_shm = shm_open(shmname, O_RDONLY, 0); + if (logbuffer_shm == -1) + err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname); + + char *logbuffer = mmap(NULL, logbuffer_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0); + if (logbuffer == MAP_FAILED) + err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log"); + + int chars; + char *walk = logbuffer + offset_next_write; + /* Skip the first line, it very likely is mangled. Not a problem, though, + * the log is chatty enough to have plenty lines left. */ + while (*walk != '\0') + walk++; + + /* Print the oldest log lines. We use printf("%s") to stop on \0. */ + while (walk < (logbuffer + offset_last_wrap)) { + chars = printf("%s", walk); + /* Shortcut: If there are two consecutive \0 bytes, this part of the + * buffer was never touched. To not call printf() for every byte of the + * buffer, we directly exit the loop. */ + if (*walk == '\0' && *(walk+1) == '\0') + break; + walk += (chars > 0 ? chars : 1); + } + + /* Then start from the beginning and print the newer lines */ + walk = logbuffer; + while (walk < (logbuffer + offset_next_write)) { + chars = printf("%s", walk); + walk += (chars > 0 ? chars : 1); + } + + return 0; +} diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index d97807d1..f494cbd5 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -14,7 +14,4 @@ while (0) extern xcb_window_t root; -char *convert_ucs_to_utf8(char *input); -char *convert_utf8_to_ucs2(char *input, int *real_strlen); - #endif diff --git a/i3-input/main.c b/i3-input/main.c index def68481..25ccceaa 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -50,7 +50,7 @@ static char *glyphs_utf8[512]; 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; @@ -94,7 +94,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t 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) { @@ -104,8 +106,8 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t 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); @@ -260,14 +262,14 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press 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) @@ -348,7 +350,7 @@ int main(int argc, char *argv[]) { 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); @@ -361,6 +363,7 @@ int main(int argc, char *argv[]) { symbols = xcb_key_symbols_alloc(conn); font = load_font(pattern, true); + set_font(&font); /* Open an input window */ win = xcb_generate_id(conn); @@ -393,9 +396,6 @@ int main(int argc, char *argv[]) { * 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); diff --git a/i3-input/ucs2_to_utf8.c b/i3-input/ucs2_to_utf8.c deleted file mode 100644 index df112eef..00000000 --- a/i3-input/ucs2_to_utf8.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#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; -} - diff --git a/i3-msg/main.c b/i3-msg/main.c index 6a3b29d3..e16f6943 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -73,9 +73,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; else if (strcasecmp(optarg, "get_bar_config") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; + else if (strcasecmp(optarg, "get_log_markers") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_LOG_MARKERS; else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_log_markers\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 4d4e253a..1dbd7736 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -131,17 +131,15 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { 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); @@ -159,12 +157,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { }; 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; @@ -193,9 +189,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { 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; } @@ -256,7 +252,7 @@ int main(int argc, char *argv[]) { break; case 'h': printf("i3-nagbar " I3_VERSION "\n"); - printf("i3-nagbar [-m ] [-b