Merge branch 'next'

Conflicts:
	include/data.h
	src/config.c
	src/handlers.c
	src/layout.c
This commit is contained in:
Michael Stapelberg 2009-11-09 22:55:24 +01:00
commit 50739cdd58
73 changed files with 25616 additions and 526 deletions

View File

@ -6,6 +6,7 @@ In that case, please try using the versions mentioned below until a fix is provi
* libxcb-1.1.93 (2008-12-11)
* xcb-util-0.3.3 (2009-01-31)
* libev
* flex and bison
* asciidoc >= 8.3.0 for docs/hacking-howto
* asciidoc, xmlto, docbook-xml for man/i3.man
* Xlib, the one that comes with your X-Server
@ -19,6 +20,8 @@ http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2
http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2
http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
http://libev.schmorp.de/
http://flex.sourceforge.net/
http://www.gnu.org/software/bison/
http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu

View File

@ -3,7 +3,9 @@ TOPDIR=$(shell pwd)
include $(TOPDIR)/common.mk
# Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard src/*.c))
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
FILES:=$(FILES:.c=.o)
HEADERS=$(wildcard include/*.h)
# Depend on the specific file (.c for each .o) and on all headers
@ -11,12 +13,24 @@ src/%.o: src/%.c ${HEADERS}
echo "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
all: ${FILES}
all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
echo "LINK i3"
$(CC) -o i3 ${FILES} $(LDFLAGS)
$(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS)
echo ""
echo "SUBDIR i3-msg"
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg
echo "SUBDIR i3-input"
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input
src/cfgparse.yy.o: src/cfgparse.l
echo "LEX $<"
flex -i -o$(@:.o=.c) $<
$(CC) $(CFLAGS) -c -o $@ $(@:.o=.c)
src/cfgparse.y.o: src/cfgparse.y
echo "YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $<
$(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
install: all
echo "INSTALL"
@ -25,32 +39,38 @@ install: all
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions
$(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/
test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)/etc/i3/welcome
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input install
dist: distclean
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION}
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop pseudo-doc.doxygen Makefile i3-${VERSION}
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION}
cp -r src i3-msg include man i3-${VERSION}
# Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs
find docs -maxdepth 1 -type f ! -name "*.xcf" -exec cp '{}' i3-${VERSION}/docs \;
sed -e 's/^GIT_VERSION=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
# Only copy source code from i3-input
mkdir i3-${VERSION}/i3-input
find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \;
sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk
# Pre-generate a manpage to allow distributors to skip this step and save some dependencies
make -C man
cp man/i3.1 i3-${VERSION}/man/i3.1
tar cf i3-${VERSION}.tar i3-${VERSION}
bzip2 -9 i3-${VERSION}.tar
cp man/*.1 i3-${VERSION}/man/
tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION}
rm -rf i3-${VERSION}
clean:
rm -f src/*.o
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c
$(MAKE) -C docs clean
$(MAKE) -C man clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input clean
distclean: clean
rm -f i3
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input distclean

62
RELEASE-NOTES-3.d Normal file
View File

@ -0,0 +1,62 @@
Release notes for i3 v3.δ
-----------------------------
This is the third version (3.δ, transcribed 3.d) of i3. It is considered stable.
This release features tabbing and some more advanced modifications of the
stacking window (see the users guide), vim-like marks, support for the
urgency hint, horizontal resizing of containers (finally), modes (which can
make your keybindings a lot simpler), an unlimited amount of workspaces
and several bugfixes (see below for the complete list of changes).
Furthermore, the configuration file parsing has been rewritten to use a
lex/yacc based lexer/parser. This makes our configuration file more easy to
understand and more flexible from the point of view of a developer. For some
of the new features, you already need the new lexer/parser. To not break your
current configuration, however, the old parser is still included and used by
default. I strongly recommend you to add the flag -l when starting i3 and
switch your configuration file to the new lexer/parser. This should only
require minor changes, if at all. In the next released version of i3, the
old configuration file parsing will be removed!
Also, this release includes the testcases which were developed in a separate
branch so far. They use Perl, together with X11::XCB, which you can download
from CPAN. Please make sure you are not doing anything important when running
the testcases, as they may modify your layout and use different workspaces.
They also might, of course, actually find bugs and crash i3 ;-).
Thanks for this release go out to xeen, mist, badboy, Mikael, mxf, Atsutane,
tsdh, litemotiv, shatter, msi, yurifury, dirkson, Scytale, Grauwolf and all
other people who reported bugs/made suggestions.
A list of changes follows:
* Implement tabbing (command "T")
* Implement horizontal resize of containers (containers! not windows)
* Implement the urgency hint for windows/workspaces
* Implement vim-like marks (mark/goto command)
* Implement stack-limit for further defining how stack windows should look
* Implement modes which allow you to use a different set of keybindings
when inside a specific mode
* Implement changing the default mode of containers
* Implement long options (--version, --no-autostart, --help, --config)
* Implement 'bt' to toggle between the different border styles
* Implement an option to specify the default border style
* Use a yacc/lex parser/lexer for the configuration file
* The number of workspaces is now dynamic instead of limited to 10
* Floating windows (and tiled containers) can now be resized using
floating_modifier and right mouse button
* Dock windows can now reconfigure their height
* Bugfix: Correctly handle multiple messages on the IPC socket
* Bugfix: Correctly use base_width, base_height and size increment hints
* Bugfix: Correctly send fake configure_notify events
* Bugfix: Dont crash if the numlock symbol cannot be found
* Bugfix: Dont display a colon after unnamed workspaces
* Bugfix: If the pointer is outside of the screen when starting, fall back to
the first screen.
* Bugfix: Initialize screens correctly when not using Xinerama
* Bugfix: Correctly handle unmap_notify events when resizing
* Bugfix: Correctly warp pointer after rendering the layout
* Bugfix: Fix NULL pointer dereference when reconfiguring screens
-- Michael Stapelberg, 2009-11-09

View File

@ -1,8 +1,8 @@
UNAME=$(shell uname)
DEBUG=1
INSTALL=install
GIT_VERSION=$(shell git describe --tags --always)
VERSION=$(shell git describe --tags --abbrev=0)
GIT_VERSION:=$(shell git describe --tags --always)
VERSION:=$(shell git describe --tags --abbrev=0)
CFLAGS += -std=c99
CFLAGS += -pipe
@ -36,6 +36,7 @@ LDFLAGS += -lxcb-atom
LDFLAGS += -lxcb-aux
LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama
LDFLAGS += -lxcb
LDFLAGS += -lX11
LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
@ -46,11 +47,18 @@ CFLAGS += -idirafter /usr/pkg/include
LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
endif
ifeq ($(UNAME),OpenBSD)
CFLAGS += -ftrampolines
CFLAGS += -I${X11BASE}/include
LDFLAGS += -liconv
LDFLAGS += -L${X11BASE}/lib
endif
ifeq ($(UNAME),FreeBSD)
LDFLAGS += -liconv
endif
ifeq ($(UNAME),Linux)
ifneq (,$(filter Linux GNU GNU/%, $(UNAME)))
CFLAGS += -D_GNU_SOURCE
endif

40
debian/changelog vendored
View File

@ -1,3 +1,43 @@
i3-wm (3.d-1) unstable; urgency=low
* Implement tabbing (command "T")
* Implement horizontal resize of containers (containers! not windows)
* Implement the urgency hint for windows/workspaces
* Implement vim-like marks (mark/goto command)
* Implement stack-limit for further defining how stack windows should look
* Implement modes which allow you to use a different set of keybindings
when inside a specific mode
* Implement changing the default mode of containers
* Implement long options (--version, --no-autostart, --help, --config)
* Implement 'bt' to toggle between the different border styles
* Implement an option to specify the default border style
* Use a yacc/lex parser/lexer for the configuration file
* The number of workspaces is now dynamic instead of limited to 10
* Floating windows (and tiled containers) can now be resized using
floating_modifier and right mouse button
* Dock windows can now reconfigure their height
* Bugfix: Correctly handle multiple messages on the IPC socket
* Bugfix: Correctly use base_width, base_height and size increment hints
* Bugfix: Correctly send fake configure_notify events
* Bugfix: Dont crash if the numlock symbol cannot be found
* Bugfix: Dont display a colon after unnamed workspaces
* Bugfix: If the pointer is outside of the screen when starting, fall back to
the first screen.
* Bugfix: Initialize screens correctly when not using Xinerama
* Bugfix: Correctly handle unmap_notify events when resizing
* Bugfix: Correctly warp pointer after rendering the layout
* Bugfix: Fix NULL pointer dereference when reconfiguring screens
* Explicitly specify -lxcb when linking (Closes: #554860)
-- Michael Stapelberg <michael@stapelberg.de> Mon, 09 Nov 2009 20:53:43 +0100
i3-wm (3.c-2) unstable; urgency=low
* Fix FTBFS on GNU/kFreeBSD and possibly GNU/Hurd (Closes: #542877)
* Add manpage for i3-msg
-- Michael Stapelberg <michael@stapelberg.de> Mon, 24 Aug 2009 12:23:18 +0200
i3-wm (3.c-1) unstable; urgency=low
* Implement a reload command

5
debian/control vendored
View File

@ -3,8 +3,8 @@ Section: utils
Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev
Standards-Version: 3.8.2
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison
Standards-Version: 3.8.3
Homepage: http://i3.zekjur.net/
Package: i3
@ -27,6 +27,7 @@ Section: x11
Depends: ${shlibs:Depends}, ${misc:Depends}
Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base
Description: an improved dynamic tiling window manager
Key features of i3 are correct implementation of Xinerama (workspaces are
assigned to virtual screens, i3 does the right thing when attaching new

2
debian/rules vendored
View File

@ -43,6 +43,8 @@ install: build
$(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
# Build architecture-independent files here.

View File

@ -299,18 +299,21 @@ to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used.
== What happens when an application is started?
i3 does not care for applications. All it notices is when new windows are mapped (see
`src/handlers.c`, `handle_map_notify_event()`). The window is then reparented (see section
`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section
"Manage windows").
After reparenting the window, `render_layout()` is called which renders the internal
layout table. The window was placed in the currently focused container and
therefore the new window and the old windows (if any) need te be moved/resized
therefore the new window and the old windows (if any) need to be moved/resized
so that the currently active layout (default mode/stacking mode) is rendered
correctly. To move/resize windows, a window is ``configured'' in X11-speak.
Some applications, such as MPlayer obivously assume the window manager is stupid
and therefore configure their windows by themselves. This generates an event called
configurenotify. i3 handles these events and pushes the window back to its position/size.
and try to configure their windows by themselves. This generates an event called
configurerequest. i3 handles these events and tells the window the size it had
before the configurerequest (with the exception of not yet mapped windows, which
get configured like they want to, and floating windows, which can reconfigure
themselves).
== _NET_WM_STATE

BIN
docs/modes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
docs/stacklimit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -46,13 +46,22 @@ image:two_columns.png[Two columns]
=== Changing mode of containers
A container can be in two modes at the moment (more to be implemented later):
+default+ or +stacking+. In default mode, clients are sized so that every client
gets an equal amount of space of the container. In stacking mode, only one
focused client of the container is displayed and you get a list of windows
at the top of the container.
A container can be in different modes:
To switch the mode, press +Mod1+h+ for stacking and +Mod1+e+ for default.
default::
Windows are sized so that every window gets an equal amount of space of the
container.
stacking::
Only the focused client of the container is displayed and you get a list of
windows at the top of the container.
tabbed::
The same principle as +stacking+, but the list of windows at the top is only
a single line which will be vertically split.
To switch the mode, press +Mod1+e+ for default, +Mod1+h+ for stacking and
+Mod1+w+ for tabbed.
image:modes.png[Container modes]
=== Toggling fullscreen mode for a window
@ -102,12 +111,14 @@ To move a window to another workspace, simply press +Mod1+Shift+num+ where
Similarly to switching workspaces, the target workspace will be created if
it does not yet exist.
=== Resizing columns
=== Resizing columns/rows
To resize columns just grab the border between the two columns and move it to
the wanted size.
To resize columns or rows just grab the border between the two columns/rows
and move it to the wanted size. Please keep in mind that each cell of the table
holds a +container+ and thus you cannot horizontally resize single windows.
A command for doing this via keyboard will be implemented soon.
See <<resizingconfig>> for how to configure i3 to be able to resize
columns/rows with your keyboard.
=== Restarting i3 inplace
@ -162,6 +173,11 @@ you can set specific applications to start on a specific workspace, you can
automatically start applications, you can change the colors of i3 or bind
your keys to do useful stuff.
To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+
and edit it with a text editor.
=== General configuration
terminal::
Specifies the terminal emulator program you prefer. It will be started
by default when you press Mod1+Enter, but you can overwrite this. Refer
@ -201,10 +217,10 @@ bind [Modifiers+]keycode command
*Examples*:
--------------------------------
# Fullscreen
bindsym Mod1+f f
bind Mod1+f f
# Restart
bindsym Mod1+Shift+r restart
bind Mod1+Shift+r restart
# Notebook-specific hotkeys
bind 214 exec /home/michael/toggle_beamer.sh
@ -241,6 +257,37 @@ floating_modifier <Modifiers>
floating_modifier Mod1
--------------------------------
=== Layout mode for new containers
This option is only available when using the new lexer/parser (pass +-l+ to i3
when starting). It determines in which mode new containers will start. See also
<<stack-limit>>.
*Syntax*:
---------------------------------------------
new_container <default|stacking|tabbed>
new_container stack-limit <cols|rows> <value>
---------------------------------------------
*Examples*:
---------------------
new_container tabbed
---------------------
=== Border style for new windows
This option is only available when using the new lexer/parser (pass +-l+ to i3
when starting). It determines which border new windows will have.
*Syntax*:
---------------------------------------------
new_window <bp|bn|bb>
---------------------------------------------
*Examples*:
---------------------
new_window bp
---------------------
=== Variables
@ -380,10 +427,14 @@ client.focused_inactive::
the focus at the moment.
client.unfocused::
A client which is not the focused one of its container.
client.urgent::
A client which has its urgency hint activated.
bar.focused::
The current workspace in the bottom bar.
bar.unfocused::
All other workspaces in the bottom bar.
bar.urgent::
A workspace which has at least one client with an activated urgency hint.
Colors are in HTML hex format, see below.
@ -411,20 +462,21 @@ section.
=== Manipulating layout
To change the layout of the current container to stacking or back to default
layout, use +s+ or +d+. To make the current client (!) fullscreen, use +f+, to
make it floating (or tiling again) use +t+:
To change the layout of the current container to stacking, use +s+, for default
use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen,
use +f+, to make it floating (or tiling again) use +t+:
*Examples*:
--------------
bindsym Mod1+s s
bindsym Mod1+l d
bindsym Mod1+w T
# Toggle fullscreen
bindsym Mod1+f f
# Toggle floating/tiling
bindsym Mod1+space t
bindsym Mod1+t t
--------------
=== Focussing/Moving/Snapping clients/containers/screens
@ -441,19 +493,19 @@ with +m+ when moving and with +s+ when snapping:
# Focus clients on the left, bottom, top, right:
bindsym Mod1+j h
bindsym Mod1+k j
bindsym Mod1+l k
bindsym Mod1+j k
bindsym Mod1+semicolon l
# Move client to the left, bottom, top, right:
bindsym Mod1+j mh
bindsym Mod1+k mj
bindsym Mod1+l mk
bindsym Mod1+j mk
bindsym Mod1+semicolon ml
# Snap client to the left, bottom, top, right:
bindsym Mod1+j sh
bindsym Mod1+k sj
bindsym Mod1+l sk
bindsym Mod1+j sk
bindsym Mod1+semicolon sl
# Focus container on the left, bottom, top, right:
@ -485,6 +537,39 @@ bindsym Mod1+o nw
bindsym Mod1+p pw
-------------------------
[[resizingconfig]]
=== Resizing columns/rows
If you want to resize columns/rows using your keyboard, you can use the
+resize+ command, I recommend using it a +mode+ (you need to use the new
lexer/parser for that, so pass +-l+ to i3 when starting):
.Example: Configuration file, defining a mode for resizing
----------------------------------------------------------------------
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
# They resize the border in the direction you pressed, e.g.
# when pressing left, the window is resized so that it has
# more space on its left
bindsym n resize left -10
bindsym Shift+n resize left +10
bindsym r resize bottom +10
bindsym Shift+r resize bottom -10
bindsym t resize top -10
bindsym Shift+t resize top +10
bindsym d resize right +10
bindsym Shift+d resize right -10
bind 36 mode default
}
----------------------------------------------------------------------
=== Jumping to specific windows
Especially when in a multi-monitor environment, you want to quickly jump to a specific
@ -508,6 +593,37 @@ or you can specify the position of the client if you always use the same layout.
bindsym Mod1+a jump "urxvt/VIM"
--------------------------------------
=== VIM-like marks (mark/goto)
This feature is like the jump feature: It allows you to directly jump to a
specific window (this means switching to the appropriate workspace and setting
focus to the windows). However, you can directly mark a specific window with
an arbitrary label and use it afterwards, that is, you do not need to ensure
that your windows have unique classes or titles and you do not need to change
your configuration file.
As the command needs to include the label with which you want to mark the
window, you cannot simply bind it to a key (or, you could bind it to a key and
only use the set of labels for which you created bindings). +i3-input+ is a
tool created for this purpose: It lets you input a command and sends the
command to i3. It can also prefix this command and display a custom prompt for
the input dialog.
*Syntax*:
-----------------
mark <identifier>
goto <identifier>
-----------------
*Examples*:
---------------------------------------
# Read 1 character and mark the current window with this character
bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
# Read 1 character and go to the window with the character
bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
---------------------------------------
=== Traveling the focus stack
This mechanism can be thought of as the opposite of the +jump+ command. It travels
@ -535,7 +651,8 @@ ft::
To change the border of the current client, you can use +bn+ to use the normal
border (including window title), +bp+ to use a 1-pixel border (no window title)
and +bb+ to make the client borderless.
and +bb+ to make the client borderless. There also is +bt+ which will toggle
the different border styles.
*Examples*:
------------------
@ -544,6 +661,35 @@ bindsym Mod1+y bp
bindsym Mod1+u bb
------------------
[[stack-limit]]
=== Changing the stack-limit of a container
If you have a single container with a lot of windows inside (say, more than
10), the default layout of a stacking container can get a little unhandy.
Depending on your screens size, you might end up only using half of the
titlebars of each window in the container.
Using the +stack-limit+ command, you can limit the amount of rows or columns
in a stacking container. i3 will create columns or rows (depending on what
you limited) automatically as needed.
*Syntax*:
--------------------------------
stack-limit <cols|rows> <value>
--------------------------------
*Examples*:
-------------------
# I always want to have two window titles in one line
stack-limit cols 2
# Not more than 5 rows in this stacking container
stack-limit rows 5
-------------------
image:stacklimit.png[Container limited to two columns]
=== Reloading/Restarting/Exiting
You can make i3 reload its configuration file with +reload+. You can also

28
i3-input/Makefile Normal file
View File

@ -0,0 +1,28 @@
# Default value so one can compile i3-input standalone
TOPDIR=..
include $(TOPDIR)/common.mk
# 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 "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
all: ${FILES}
echo "LINK i3-input"
$(CC) -o i3-input ${FILES} $(LDFLAGS)
install: all
echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
$(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/
clean:
rm -f *.o
distclean: clean
rm -f i3-input

19336
i3-input/UnicodeData.txt Normal file

File diff suppressed because it is too large Load Diff

178
i3-input/convmap.pl Executable file
View File

@ -0,0 +1,178 @@
#!/usr/bin/perl
# Generate keysym2ucs.c file
#
# $XFree86: xc/programs/xterm/unicode/convmap.pl,v 1.5 2000/01/24 22:22:05 dawes Exp $
sub utf8 ($) {
my $c = shift(@_);
if ($c < 0x80) {
return sprintf("%c", $c);
} elsif ($c < 0x800) {
return sprintf("%c%c", 0xc0 | ($c >> 6), 0x80 | ($c & 0x3f));
} elsif ($c < 0x10000) {
return sprintf("%c%c%c",
0xe0 | ($c >> 12),
0x80 | (($c >> 6) & 0x3f),
0x80 | ( $c & 0x3f));
} elsif ($c < 0x200000) {
return sprintf("%c%c%c%c",
0xf0 | ($c >> 18),
0x80 | (($c >> 12) & 0x3f),
0x80 | (($c >> 6) & 0x3f),
0x80 | ( $c & 0x3f));
} elsif ($c < 0x4000000) {
return sprintf("%c%c%c%c%c",
0xf8 | ($c >> 24),
0x80 | (($c >> 18) & 0x3f),
0x80 | (($c >> 12) & 0x3f),
0x80 | (($c >> 6) & 0x3f),
0x80 | ( $c & 0x3f));
} elsif ($c < 0x80000000) {
return sprintf("%c%c%c%c%c%c",
0xfe | ($c >> 30),
0x80 | (($c >> 24) & 0x3f),
0x80 | (($c >> 18) & 0x3f),
0x80 | (($c >> 12) & 0x3f),
0x80 | (($c >> 6) & 0x3f),
0x80 | ( $c & 0x3f));
} else {
return utf8(0xfffd);
}
}
$unicodedata = "UnicodeData.txt";
# read list of all Unicode names
if (!open(UDATA, $unicodedata) && !open(UDATA, "$unicodedata")) {
die ("Can't open Unicode database '$unicodedata':\n$!\n\n" .
"Please make sure that you have downloaded the file\n" .
"ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData-Latest.txt\n");
}
while (<UDATA>) {
if (/^([0-9,A-F]{4});([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*)$/) {
$name{hex($1)} = $2;
} else {
next;
die("Syntax error in line '$_' in file '$unicodedata'");
}
}
close(UDATA);
# read mapping (from http://wsinwp07.win.tue.nl:1234/unicode/keysym.map)
open(LIST, "<keysym.map") || die ("Can't open map file:\n$!\n");
while (<LIST>) {
if (/^0x([0-9a-f]{4})\s+U([0-9a-f]{4})\s*(\#.*)?$/){
$keysym = hex($1);
$ucs = hex($2);
$keysym_to_ucs{$keysym} = $ucs;
} elsif (/^\s*\#/ || /^\s*$/) {
} else {
die("Syntax error in 'list' in line\n$_\n");
}
}
close(LIST);
# read entries in keysymdef.h
open(LIST, "</usr/include/X11/keysymdef.h") || die ("Can't open keysymdef.h:\n$!\n");
while (<LIST>) {
if (/^\#define\s+XK_([A-Za-z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/.*)?$/) {
next if /\/\* deprecated \*\//;
$keysymname = $1;
$keysym = hex($2);
$keysym_to_keysymname{$keysym} = $keysymname;
}
}
close(LIST);
print <<EOT;
/* \$XFree86\$
* This module converts keysym values into the corresponding ISO 10646-1
* (UCS, Unicode) values.
*
* The array keysymtab[] contains pairs of X11 keysym values for graphical
* characters and the corresponding Unicode value. The function
* keysym2ucs() maps a keysym onto a Unicode value using a binary search,
* therefore keysymtab[] must remain SORTED by keysym value.
*
* The keysym -> UTF-8 conversion will hopefully one day be provided
* by Xlib via XmbLookupString() and should ideally not have to be
* done in X applications. But we are not there yet.
*
* We allow to represent any UCS character in the range U+00000000 to
* U+00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
* This admittedly does not cover the entire 31-bit space of UCS, but
* it does cover all of the characters up to U+10FFFF, which can be
* represented by UTF-16, and more, and it is very unlikely that higher
* UCS codes will ever be assigned by ISO. So to get Unicode character
* U+ABCD you can directly use keysym 0x1000abcd.
*
* NOTE: The comments in the table below contain the actual character
* encoded in UTF-8, so for viewing and editing best use an editor in
* UTF-8 mode.
*
* Author: Markus G. Kuhn <mkuhn\@acm.org>, University of Cambridge, June 1999
*
* Special thanks to Richard Verhoeven <river\@win.tue.nl> for preparing
* an initial draft of the mapping table.
*
* This software is in the public domain. Share and enjoy!
*/
#include <keysym2ucs.h>
struct codepair {
unsigned short keysym;
unsigned short ucs;
} keysymtab[] = {
EOT
for $keysym (sort {$a <=> $b} keys(%keysym_to_keysymname)) {
$ucs = $keysym_to_ucs{$keysym};
next if $keysym >= 0xf000 || $keysym < 0x100;
if ($ucs) {
printf(" { 0x%04x, 0x%04x }, /*%28s %s %s */\n",
$keysym, $ucs, $keysym_to_keysymname{$keysym}, utf8($ucs),
defined($name{$ucs}) ? $name{$ucs} : "???" );
} else {
printf("/* 0x%04x %39s ? ??? */\n",
$keysym, $keysym_to_keysymname{$keysym});
}
}
print <<EOT;
};
long keysym2ucs(KeySym keysym)
{
int min = 0;
int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
int mid;
/* first check for Latin-1 characters (1:1 mapping) */
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
(keysym >= 0x00a0 && keysym <= 0x00ff))
return keysym;
/* also check for directly encoded 24-bit UCS characters */
if ((keysym & 0xff000000) == 0x01000000)
return keysym & 0x00ffffff;
/* binary search in table */
while (max >= min) {
mid = (min + max) / 2;
if (keysymtab[mid].keysym < keysym)
min = mid + 1;
else if (keysymtab[mid].keysym > keysym)
max = mid - 1;
else {
/* found it */
return keysymtab[mid].ucs;
}
}
/* no matching Unicode value found */
return -1;
}
EOT

19
i3-input/i3-input.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef _I3_INPUT
#define _I3_INPUT
#include <err.h>
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
uint32_t get_mode_switch_mask(xcb_connection_t *conn);
int connect_ipc(char *socket_path);
void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload);
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height);
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height);
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value);
#endif

68
i3-input/ipc.c Normal file
View File

@ -0,0 +1,68 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <err.h>
/*
* Formats a message (payload) of the given size and type and sends it to i3 via
* the given socket file descriptor.
*
*/
void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload) {
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
char msg[buffer_size];
char *walk = msg;
strcpy(walk, "i3-ipc");
walk += strlen("i3-ipc");
memcpy(walk, &message_size, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, &message_type, sizeof(uint32_t));
walk += sizeof(uint32_t);
memcpy(walk, payload, message_size);
int sent_bytes = 0;
int bytes_to_go = buffer_size;
while (sent_bytes < bytes_to_go) {
int n = write(sockfd, msg + sent_bytes, bytes_to_go);
if (n == -1)
err(EXIT_FAILURE, "write() failed");
sent_bytes += n;
bytes_to_go -= n;
}
}
/*
* Connects to the i3 IPC socket and returns the file descriptor for the
* socket. die()s if anything goes wrong.
*
*/
int connect_ipc(char *socket_path) {
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;
strcpy(addr.sun_path, socket_path);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
return sockfd;
}

1062
i3-input/keysym.map Normal file

File diff suppressed because it is too large Load Diff

847
i3-input/keysym2ucs.c Normal file
View File

@ -0,0 +1,847 @@
/* $XFree86$
* This module converts keysym values into the corresponding ISO 10646-1
* (UCS, Unicode) values.
*
* The array keysymtab[] contains pairs of X11 keysym values for graphical
* characters and the corresponding Unicode value. The function
* keysym2ucs() maps a keysym onto a Unicode value using a binary search,
* therefore keysymtab[] must remain SORTED by keysym value.
*
* The keysym -> UTF-8 conversion will hopefully one day be provided
* by Xlib via XmbLookupString() and should ideally not have to be
* done in X applications. But we are not there yet.
*
* We allow to represent any UCS character in the range U+00000000 to
* U+00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
* This admittedly does not cover the entire 31-bit space of UCS, but
* it does cover all of the characters up to U+10FFFF, which can be
* represented by UTF-16, and more, and it is very unlikely that higher
* UCS codes will ever be assigned by ISO. So to get Unicode character
* U+ABCD you can directly use keysym 0x1000abcd.
*
* NOTE: The comments in the table below contain the actual character
* encoded in UTF-8, so for viewing and editing best use an editor in
* UTF-8 mode.
*
* Author: Markus G. Kuhn <mkuhn@acm.org>, University of Cambridge, June 1999
*
* Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
* an initial draft of the mapping table.
*
* This software is in the public domain. Share and enjoy!
*/
#include <xcb/xcb.h>
#include "keysym2ucs.h"
struct codepair {
unsigned short keysym;
unsigned short ucs;
} keysymtab[] = {
{ 0x01a1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
{ 0x01a2, 0x02d8 }, /* breve ˘ BREVE */
{ 0x01a3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
{ 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
{ 0x01a6, 0x015a }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
{ 0x01a9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */
{ 0x01aa, 0x015e }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
{ 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
{ 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
{ 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
{ 0x01af, 0x017b }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
{ 0x01b1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */
{ 0x01b2, 0x02db }, /* ogonek ˛ OGONEK */
{ 0x01b3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */
{ 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */
{ 0x01b6, 0x015b }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */
{ 0x01b7, 0x02c7 }, /* caron ˇ CARON */
{ 0x01b9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */
{ 0x01ba, 0x015f }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
{ 0x01bb, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */
{ 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */
{ 0x01bd, 0x02dd }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */
{ 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */
{ 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
{ 0x01c0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
{ 0x01c3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
{ 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
{ 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
{ 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
{ 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
{ 0x01cc, 0x011a }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
{ 0x01cf, 0x010e }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
{ 0x01d0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
{ 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
{ 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
{ 0x01d5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
{ 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
{ 0x01d9, 0x016e }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
{ 0x01db, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
{ 0x01de, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
{ 0x01e0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */
{ 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */
{ 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
{ 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */
{ 0x01e8, 0x010d }, /* ccaron č LATIN SMALL LETTER C WITH CARON */
{ 0x01ea, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */
{ 0x01ec, 0x011b }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */
{ 0x01ef, 0x010f }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */
{ 0x01f0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */
{ 0x01f1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */
{ 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */
{ 0x01f5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
{ 0x01f8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */
{ 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */
{ 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
{ 0x01fe, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
{ 0x01ff, 0x02d9 }, /* abovedot ˙ DOT ABOVE */
{ 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
{ 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
{ 0x02a9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
{ 0x02ab, 0x011e }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
{ 0x02ac, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
{ 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */
{ 0x02b6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
{ 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */
{ 0x02bb, 0x011f }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */
{ 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
{ 0x02c5, 0x010a }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
{ 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
{ 0x02d5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
{ 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
{ 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
{ 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
{ 0x02e5, 0x010b }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
{ 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
{ 0x02f5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
{ 0x02f8, 0x011d }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
{ 0x02fd, 0x016d }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
{ 0x02fe, 0x015d }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
{ 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */
{ 0x03a3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
{ 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
{ 0x03a6, 0x013b }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
{ 0x03aa, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
{ 0x03ab, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
{ 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
{ 0x03b3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
{ 0x03b5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */
{ 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
{ 0x03ba, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */
{ 0x03bb, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
{ 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */
{ 0x03bd, 0x014a }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */
{ 0x03bf, 0x014b }, /* eng ŋ LATIN SMALL LETTER ENG */
{ 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
{ 0x03c7, 0x012e }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
{ 0x03cc, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
{ 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
{ 0x03d1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
{ 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
{ 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
{ 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
{ 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
{ 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
{ 0x03e0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */
{ 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */
{ 0x03ec, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
{ 0x03ef, 0x012b }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */
{ 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
{ 0x03f2, 0x014d }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */
{ 0x03f3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
{ 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */
{ 0x03fd, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */
{ 0x03fe, 0x016b }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */
{ 0x047e, 0x203e }, /* overline ‾ OVERLINE */
{ 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */
{ 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */
{ 0x04a3, 0x300d }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */
{ 0x04a4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */
{ 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */
{ 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */
{ 0x04a7, 0x30a1 }, /* kana_a ァ KATAKANA LETTER SMALL A */
{ 0x04a8, 0x30a3 }, /* kana_i ィ KATAKANA LETTER SMALL I */
{ 0x04a9, 0x30a5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */
{ 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */
{ 0x04ab, 0x30a9 }, /* kana_o ォ KATAKANA LETTER SMALL O */
{ 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */
{ 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */
{ 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */
{ 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */
{ 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
{ 0x04b1, 0x30a2 }, /* kana_A ア KATAKANA LETTER A */
{ 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */
{ 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */
{ 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */
{ 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */
{ 0x04b6, 0x30ab }, /* kana_KA カ KATAKANA LETTER KA */
{ 0x04b7, 0x30ad }, /* kana_KI キ KATAKANA LETTER KI */
{ 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */
{ 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */
{ 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */
{ 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */
{ 0x04bc, 0x30b7 }, /* kana_SHI シ KATAKANA LETTER SI */
{ 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */
{ 0x04be, 0x30bb }, /* kana_SE セ KATAKANA LETTER SE */
{ 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */
{ 0x04c0, 0x30bf }, /* kana_TA タ KATAKANA LETTER TA */
{ 0x04c1, 0x30c1 }, /* kana_CHI チ KATAKANA LETTER TI */
{ 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */
{ 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */
{ 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */
{ 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */
{ 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */
{ 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */
{ 0x04c8, 0x30cd }, /* kana_NE ネ KATAKANA LETTER NE */
{ 0x04c9, 0x30ce }, /* kana_NO KATAKANA LETTER NO */
{ 0x04ca, 0x30cf }, /* kana_HA ハ KATAKANA LETTER HA */
{ 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */
{ 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */
{ 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */
{ 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */
{ 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */
{ 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */
{ 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */
{ 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */
{ 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */
{ 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */
{ 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */
{ 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */
{ 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */
{ 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */
{ 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */
{ 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */
{ 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */
{ 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */
{ 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */
{ 0x04de, 0x309b }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
{ 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
{ 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */
{ 0x05bb, 0x061b }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */
{ 0x05bf, 0x061f }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */
{ 0x05c1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */
{ 0x05c2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
{ 0x05c3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
{ 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
{ 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
{ 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
{ 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */
{ 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */
{ 0x05c9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
{ 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */
{ 0x05cb, 0x062b }, /* Arabic_theh ث ARABIC LETTER THEH */
{ 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */
{ 0x05cd, 0x062d }, /* Arabic_hah ح ARABIC LETTER HAH */
{ 0x05ce, 0x062e }, /* Arabic_khah خ ARABIC LETTER KHAH */
{ 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */
{ 0x05d0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */
{ 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */
{ 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */
{ 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */
{ 0x05d4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */
{ 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */
{ 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */
{ 0x05d7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */
{ 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */
{ 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */
{ 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */
{ 0x05e0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */
{ 0x05e1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */
{ 0x05e2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */
{ 0x05e3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */
{ 0x05e4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */
{ 0x05e5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */
{ 0x05e6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */
{ 0x05e7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */
{ 0x05e8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */
{ 0x05e9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
{ 0x05ea, 0x064a }, /* Arabic_yeh ي ARABIC LETTER YEH */
{ 0x05eb, 0x064b }, /* Arabic_fathatan ً ARABIC FATHATAN */
{ 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */
{ 0x05ed, 0x064d }, /* Arabic_kasratan ٍ ARABIC KASRATAN */
{ 0x05ee, 0x064e }, /* Arabic_fatha َ ARABIC FATHA */
{ 0x05ef, 0x064f }, /* Arabic_damma ُ ARABIC DAMMA */
{ 0x05f0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */
{ 0x05f1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */
{ 0x05f2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */
{ 0x06a1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
{ 0x06a2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
{ 0x06a3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */
{ 0x06a4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
{ 0x06a5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
{ 0x06a6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06a7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
{ 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */
{ 0x06a9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
{ 0x06aa, 0x045a }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
{ 0x06ab, 0x045b }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
{ 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
/* 0x06ad Ukrainian_ghe_with_upturn ? ??? */
{ 0x06ae, 0x045e }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
{ 0x06af, 0x045f }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
{ 0x06b0, 0x2116 }, /* numerosign № NUMERO SIGN */
{ 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
{ 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
{ 0x06b3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
{ 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
{ 0x06b5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
{ 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
{ 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
{ 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
{ 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
{ 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
{ 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
/* 0x06bd Ukrainian_GHE_WITH_UPTURN ? ??? */
{ 0x06be, 0x040e }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
{ 0x06bf, 0x040f }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
{ 0x06c0, 0x044e }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
{ 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */
{ 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */
{ 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
{ 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */
{ 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */
{ 0x06c6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
{ 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
{ 0x06c8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */
{ 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */
{ 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
{ 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */
{ 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */
{ 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */
{ 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */
{ 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */
{ 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */
{ 0x06d1, 0x044f }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */
{ 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */
{ 0x06d3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */
{ 0x06d4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */
{ 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */
{ 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
{ 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */
{ 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
{ 0x06d9, 0x044b }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
{ 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
{ 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
{ 0x06dc, 0x044d }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */
{ 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
{ 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
{ 0x06df, 0x044a }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
{ 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
{ 0x06e1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */
{ 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
{ 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
{ 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
{ 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
{ 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
{ 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
{ 0x06e8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
{ 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */
{ 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
{ 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
{ 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
{ 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
{ 0x06ee, 0x041d }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
{ 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */
{ 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
{ 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
{ 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
{ 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
{ 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
{ 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */
{ 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
{ 0x06f7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
{ 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
{ 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
{ 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
{ 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
{ 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
{ 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
{ 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
{ 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
{ 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
{ 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
{ 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
{ 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
{ 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
{ 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
{ 0x07a8, 0x038e }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
{ 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ab, 0x038f }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
{ 0x07ae, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
{ 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */
{ 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
{ 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
{ 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
{ 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
{ 0x07b5, 0x03ca }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
{ 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
{ 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
{ 0x07b8, 0x03cd }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
{ 0x07b9, 0x03cb }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
{ 0x07bb, 0x03ce }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
{ 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
{ 0x07c2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */
{ 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
{ 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
{ 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
{ 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
{ 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */
{ 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */
{ 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
{ 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
{ 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
{ 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */
{ 0x07cd, 0x039d }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */
{ 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */
{ 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
{ 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */
{ 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
{ 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
{ 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */
{ 0x07d5, 0x03a5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
{ 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */
{ 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */
{ 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
{ 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
{ 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */
{ 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */
{ 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */
{ 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */
{ 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */
{ 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */
{ 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */
{ 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */
{ 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */
{ 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */
{ 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */
{ 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */
{ 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */
{ 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */
{ 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */
{ 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */
{ 0x07f1, 0x03c1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */
{ 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */
{ 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
{ 0x07f4, 0x03c4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */
{ 0x07f5, 0x03c5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */
{ 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */
{ 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */
{ 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */
{ 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */
/* 0x08a1 leftradical ? ??? */
/* 0x08a2 topleftradical ? ??? */
/* 0x08a3 horizconnector ? ??? */
{ 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */
{ 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */
{ 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
/* 0x08a7 topleftsqbracket ? ??? */
/* 0x08a8 botleftsqbracket ? ??? */
/* 0x08a9 toprightsqbracket ? ??? */
/* 0x08aa botrightsqbracket ? ??? */
/* 0x08ab topleftparens ? ??? */
/* 0x08ac botleftparens ? ??? */
/* 0x08ad toprightparens ? ??? */
/* 0x08ae botrightparens ? ??? */
/* 0x08af leftmiddlecurlybrace ? ??? */
/* 0x08b0 rightmiddlecurlybrace ? ??? */
/* 0x08b1 topleftsummation ? ??? */
/* 0x08b2 botleftsummation ? ??? */
/* 0x08b3 topvertsummationconnector ? ??? */
/* 0x08b4 botvertsummationconnector ? ??? */
/* 0x08b5 toprightsummation ? ??? */
/* 0x08b6 botrightsummation ? ??? */
/* 0x08b7 rightmiddlesummation ? ??? */
{ 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */
{ 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */
{ 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
{ 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */
{ 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */
{ 0x08c1, 0x221d }, /* variation ∝ PROPORTIONAL TO */
{ 0x08c2, 0x221e }, /* infinity ∞ INFINITY */
{ 0x08c5, 0x2207 }, /* nabla ∇ NABLA */
{ 0x08c8, 0x2245 }, /* approximate ≅ APPROXIMATELY EQUAL TO */
/* 0x08c9 similarequal ? ??? */
{ 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
{ 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */
{ 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */
{ 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */
{ 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */
{ 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */
{ 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */
{ 0x08dd, 0x222a }, /* union UNION */
{ 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */
{ 0x08df, 0x2228 }, /* logicalor LOGICAL OR */
{ 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */
{ 0x08f6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */
{ 0x08fb, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */
{ 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */
{ 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */
{ 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */
{ 0x09df, 0x2422 }, /* blank ␢ BLANK SYMBOL */
{ 0x09e0, 0x25c6 }, /* soliddiamond ◆ BLACK DIAMOND */
{ 0x09e1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */
{ 0x09e2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
{ 0x09e3, 0x240c }, /* ff ␌ SYMBOL FOR FORM FEED */
{ 0x09e4, 0x240d }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */
{ 0x09e5, 0x240a }, /* lf ␊ SYMBOL FOR LINE FEED */
{ 0x09e8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */
{ 0x09e9, 0x240b }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */
{ 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
{ 0x09eb, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
{ 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
{ 0x09ed, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
{ 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
/* 0x09ef horizlinescan1 ? ??? */
/* 0x09f0 horizlinescan3 ? ??? */
{ 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
/* 0x09f2 horizlinescan7 ? ??? */
/* 0x09f3 horizlinescan9 ? ??? */
{ 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
{ 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
{ 0x09f6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
{ 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
{ 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */
{ 0x0aa1, 0x2003 }, /* emspace EM SPACE */
{ 0x0aa2, 0x2002 }, /* enspace EN SPACE */
{ 0x0aa3, 0x2004 }, /* em3space THREE-PER-EM SPACE */
{ 0x0aa4, 0x2005 }, /* em4space FOUR-PER-EM SPACE */
{ 0x0aa5, 0x2007 }, /* digitspace FIGURE SPACE */
{ 0x0aa6, 0x2008 }, /* punctspace PUNCTUATION SPACE */
{ 0x0aa7, 0x2009 }, /* thinspace THIN SPACE */
{ 0x0aa8, 0x200a }, /* hairspace HAIR SPACE */
{ 0x0aa9, 0x2014 }, /* emdash — EM DASH */
{ 0x0aaa, 0x2013 }, /* endash EN DASH */
/* 0x0aac signifblank ? ??? */
{ 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */
/* 0x0aaf doubbaselinedot ? ??? */
{ 0x0ab0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */
{ 0x0ab1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */
{ 0x0ab2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */
{ 0x0ab3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
{ 0x0ab4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
{ 0x0ab5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
{ 0x0ab6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */
{ 0x0ab7, 0x215a }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
{ 0x0ab8, 0x2105 }, /* careof ℅ CARE OF */
{ 0x0abb, 0x2012 }, /* figdash FIGURE DASH */
{ 0x0abc, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
{ 0x0abd, 0x002e }, /* decimalpoint . FULL STOP */
{ 0x0abe, 0x232a }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
/* 0x0abf marker ? ??? */
{ 0x0ac3, 0x215b }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
{ 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
{ 0x0ac5, 0x215d }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
{ 0x0ac6, 0x215e }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
{ 0x0ac9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */
{ 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */
/* 0x0acb trademarkincircle ? ??? */
{ 0x0acc, 0x25c1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
{ 0x0acd, 0x25b7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
{ 0x0ace, 0x25cb }, /* emopencircle ○ WHITE CIRCLE */
{ 0x0acf, 0x25a1 }, /* emopenrectangle □ WHITE SQUARE */
{ 0x0ad0, 0x2018 }, /* leftsinglequotemark LEFT SINGLE QUOTATION MARK */
{ 0x0ad1, 0x2019 }, /* rightsinglequotemark RIGHT SINGLE QUOTATION MARK */
{ 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
{ 0x0ad3, 0x201d }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
{ 0x0ad4, 0x211e }, /* prescription ℞ PRESCRIPTION TAKE */
{ 0x0ad6, 0x2032 }, /* minutes PRIME */
{ 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */
{ 0x0ad9, 0x271d }, /* latincross ✝ LATIN CROSS */
/* 0x0ada hexagram ? ??? */
{ 0x0adb, 0x25ac }, /* filledrectbullet ▬ BLACK RECTANGLE */
{ 0x0adc, 0x25c0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
{ 0x0add, 0x25b6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
{ 0x0ade, 0x25cf }, /* emfilledcircle ● BLACK CIRCLE */
{ 0x0adf, 0x25a0 }, /* emfilledrect ■ BLACK SQUARE */
{ 0x0ae0, 0x25e6 }, /* enopencircbullet ◦ WHITE BULLET */
{ 0x0ae1, 0x25ab }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */
{ 0x0ae2, 0x25ad }, /* openrectbullet ▭ WHITE RECTANGLE */
{ 0x0ae3, 0x25b3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */
{ 0x0ae4, 0x25bd }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
{ 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */
{ 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */
{ 0x0ae7, 0x25aa }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */
{ 0x0ae8, 0x25b2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
{ 0x0ae9, 0x25bc }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
{ 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */
{ 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */
{ 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */
{ 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */
{ 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */
{ 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */
{ 0x0af1, 0x2020 }, /* dagger † DAGGER */
{ 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */
{ 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */
{ 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */
{ 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */
{ 0x0af6, 0x266d }, /* musicalflat ♭ MUSIC FLAT SIGN */
{ 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */
{ 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */
{ 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */
{ 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */
{ 0x0afb, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
{ 0x0afc, 0x2038 }, /* caret ‸ CARET */
{ 0x0afd, 0x201a }, /* singlelowquotemark SINGLE LOW-9 QUOTATION MARK */
{ 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
/* 0x0aff cursor ? ??? */
{ 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */
{ 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */
{ 0x0ba8, 0x2228 }, /* downcaret LOGICAL OR */
{ 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */
{ 0x0bc0, 0x00af }, /* overbar ¯ MACRON */
{ 0x0bc2, 0x22a4 }, /* downtack DOWN TACK */
{ 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */
{ 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */
{ 0x0bc6, 0x005f }, /* underbar _ LOW LINE */
{ 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */
{ 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */
{ 0x0bce, 0x22a5 }, /* uptack ⊥ UP TACK */
{ 0x0bcf, 0x25cb }, /* circle ○ WHITE CIRCLE */
{ 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */
{ 0x0bd6, 0x222a }, /* downshoe UNION */
{ 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */
{ 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */
{ 0x0bdc, 0x22a3 }, /* lefttack ⊣ LEFT TACK */
{ 0x0bfc, 0x22a2 }, /* righttack ⊢ RIGHT TACK */
{ 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */
{ 0x0ce0, 0x05d0 }, /* hebrew_aleph א HEBREW LETTER ALEF */
{ 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */
{ 0x0ce2, 0x05d2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */
{ 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */
{ 0x0ce4, 0x05d4 }, /* hebrew_he ה HEBREW LETTER HE */
{ 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */
{ 0x0ce6, 0x05d6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */
{ 0x0ce7, 0x05d7 }, /* hebrew_chet ח HEBREW LETTER HET */
{ 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */
{ 0x0ce9, 0x05d9 }, /* hebrew_yod י HEBREW LETTER YOD */
{ 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
{ 0x0ceb, 0x05db }, /* hebrew_kaph כ HEBREW LETTER KAF */
{ 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */
{ 0x0ced, 0x05dd }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */
{ 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */
{ 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */
{ 0x0cf0, 0x05e0 }, /* hebrew_nun נ HEBREW LETTER NUN */
{ 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */
{ 0x0cf2, 0x05e2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */
{ 0x0cf3, 0x05e3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */
{ 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */
{ 0x0cf5, 0x05e5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
{ 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */
{ 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */
{ 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */
{ 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */
{ 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */
{ 0x0da1, 0x0e01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */
{ 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */
{ 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
{ 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
{ 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */
{ 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
{ 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */
{ 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */
{ 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */
{ 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */
{ 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */
{ 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
{ 0x0dad, 0x0e0d }, /* Thai_yoying ญ THAI CHARACTER YO YING */
{ 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */
{ 0x0daf, 0x0e0f }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */
{ 0x0db0, 0x0e10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */
{ 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
{ 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
{ 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */
{ 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */
{ 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */
{ 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */
{ 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */
{ 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */
{ 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */
{ 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
{ 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */
{ 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */
{ 0x0dbd, 0x0e1d }, /* Thai_fofa ฝ THAI CHARACTER FO FA */
{ 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */
{ 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */
{ 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
{ 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */
{ 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */
{ 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */
{ 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */
{ 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */
{ 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */
{ 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */
{ 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */
{ 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */
{ 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */
{ 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */
{ 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */
{ 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */
{ 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
{ 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
{ 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */
{ 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
{ 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */
{ 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */
{ 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */
{ 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */
{ 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */
{ 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */
{ 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */
{ 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */
{ 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */
{ 0x0dde, 0x0e3e }, /* Thai_maihanakat_maitho ฾ ??? */
{ 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
{ 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */
{ 0x0de1, 0x0e41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */
{ 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */
{ 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
{ 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
{ 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
{ 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
{ 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
{ 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */
{ 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */
{ 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */
{ 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
{ 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
{ 0x0ded, 0x0e4d }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
{ 0x0df0, 0x0e50 }, /* Thai_leksun THAI DIGIT ZERO */
{ 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */
{ 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */
{ 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */
{ 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */
{ 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */
{ 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */
{ 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */
{ 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */
{ 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */
{ 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
{ 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
{ 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
{ 0x0ea4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
{ 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
{ 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
{ 0x0ea7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
{ 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
{ 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
{ 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
{ 0x0eab, 0x313b }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
{ 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
{ 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
{ 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
{ 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
{ 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
{ 0x0eb1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
{ 0x0eb2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
{ 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
{ 0x0eb4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
{ 0x0eb5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */
{ 0x0eb6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
{ 0x0eb7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
{ 0x0eb8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
{ 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
{ 0x0eba, 0x314a }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
{ 0x0ebb, 0x314b }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
{ 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
{ 0x0ebd, 0x314d }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
{ 0x0ebe, 0x314e }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
{ 0x0ebf, 0x314f }, /* Hangul_A ㅏ HANGUL LETTER A */
{ 0x0ec0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */
{ 0x0ec1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */
{ 0x0ec2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */
{ 0x0ec3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */
{ 0x0ec4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */
{ 0x0ec5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */
{ 0x0ec6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */
{ 0x0ec7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */
{ 0x0ec8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */
{ 0x0ec9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */
{ 0x0eca, 0x315a }, /* Hangul_OE ㅚ HANGUL LETTER OE */
{ 0x0ecb, 0x315b }, /* Hangul_YO ㅛ HANGUL LETTER YO */
{ 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */
{ 0x0ecd, 0x315d }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */
{ 0x0ece, 0x315e }, /* Hangul_WE ㅞ HANGUL LETTER WE */
{ 0x0ecf, 0x315f }, /* Hangul_WI ㅟ HANGUL LETTER WI */
{ 0x0ed0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */
{ 0x0ed1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */
{ 0x0ed2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */
{ 0x0ed3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */
{ 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
{ 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
{ 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
{ 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
{ 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
{ 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
{ 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
{ 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
{ 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
{ 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
{ 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
{ 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
{ 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
{ 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
{ 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
{ 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
{ 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
{ 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
{ 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
{ 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
{ 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
{ 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
{ 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
{ 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
{ 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
{ 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
{ 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
{ 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
{ 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
{ 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
{ 0x0ef2, 0x317f }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
/* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */
{ 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
{ 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
{ 0x0ef6, 0x318d }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
{ 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
{ 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
/* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */
{ 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
{ 0x0eff, 0x20a9 }, /* Korean_Won ₩ WON SIGN */
{ 0x13bc, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */
{ 0x13bd, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */
{ 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
{ 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */
};
long keysym2ucs(xcb_keysym_t keysym)
{
int min = 0;
int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
int mid;
/* first check for Latin-1 characters (1:1 mapping) */
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
(keysym >= 0x00a0 && keysym <= 0x00ff))
return keysym;
/* also check for directly encoded 24-bit UCS characters */
if ((keysym & 0xff000000) == 0x01000000)
return keysym & 0x00ffffff;
/* binary search in table */
while (max >= min) {
mid = (min + max) / 2;
if (keysymtab[mid].keysym < keysym)
min = mid + 1;
else if (keysymtab[mid].keysym > keysym)
max = mid - 1;
else {
/* found it */
return keysymtab[mid].ucs;
}
}
/* no matching Unicode value found */
return -1;
}

3
i3-input/keysym2ucs.h Normal file
View File

@ -0,0 +1,3 @@
#include <xcb/xcb.h>
long keysym2ucs(xcb_keysym_t keysym);

320
i3-input/main.c Normal file
View File

@ -0,0 +1,320 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* i3-input/main.c: Utility which lets the user input commands and sends them
* to i3.
*
*/
#include <ev.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "keysym2ucs.h"
#include "i3-input.h"
static int sockfd;
static xcb_key_symbols_t *symbols;
static int modeswitchmask;
static bool modeswitch_active = false;
static xcb_window_t win;
static xcb_pixmap_t pixmap;
static xcb_gcontext_t pixmap_gc;
static char *glyphs_ucs[512];
static char *glyphs_utf8[512];
static int input_position;
static int font_height;
static char *command_prefix;
static char *prompt;
static int prompt_len;
static int limit;
/*
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
* rendering it (UCS-2) or sending it to i3 (UTF-8).
*
*/
static uint8_t *concat_strings(char **glyphs, int max) {
uint8_t *output = calloc(max+1, 4);
uint8_t *walk = output;
for (int c = 0; c < max; c++) {
printf("at %c\n", glyphs[c][0]);
/* if the first byte is 0, this has to be UCS2 */
if (glyphs[c][0] == '\0') {
memcpy(walk, glyphs[c], 2);
walk += 2;
} else {
strcpy((char*)walk, glyphs[c]);
walk += strlen(glyphs[c]);
}
}
printf("output = %s\n", output);
return output;
}
/*
* Handles expose events (redraws of the window) and rendering in general. Will
* be called from the code with event == NULL or from X with event != NULL.
*
*/
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
printf("expose!\n");
/* re-draw the background */
xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4};
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
/* restore font color */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
uint8_t *con = concat_strings(glyphs_ucs, input_position);
char *full_text = (char*)con;
if (prompt != NULL) {
full_text = malloc((prompt_len + input_position) * 2 + 1);
if (full_text == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
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);
/* 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);
xcb_flush(conn);
free(con);
if (prompt != NULL)
free(full_text);
return 1;
}
/*
* Deactivates the Mode_switch bit upon release of the Mode_switch key.
*
*/
static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
printf("releasing %d, state raw = %d\n", event->detail, event->state);
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) {
printf("Mode switch disabled\n");
modeswitch_active = false;
}
return 1;
}
static void finish_input() {
uint8_t *command = concat_strings(glyphs_utf8, input_position);
char *full_command = (char*)command;
/* prefix the command if a prefix was specified on commandline */
if (command_prefix != NULL) {
if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
err(EXIT_FAILURE, "asprintf() failed\n");
}
printf("command = %s\n", full_command);
ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
#if 0
free(command);
return 1;
#endif
exit(0);
}
/*
* Handles keypresses by converting the keycodes to keysymbols, then the
* keysymbols to UCS-2. If the conversion succeeded, the glyph is saved in the
* internal buffers and displayed in the input window.
*
* Also handles backspace (deleting one character) and return (sending the
* command to i3).
*
*/
static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
printf("Keypress %d, state raw = %d\n", event->detail, event->state);
/* fix state */
if (modeswitch_active)
event->state |= modeswitchmask;
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) {
printf("Mode switch enabled\n");
modeswitch_active = true;
return 1;
}
if (sym == XK_Return)
finish_input();
if (sym == XK_BackSpace) {
if (input_position == 0)
return 1;
input_position--;
free(glyphs_ucs[input_position]);
free(glyphs_utf8[input_position]);
handle_expose(NULL, conn, NULL);
return 1;
}
if (sym == XK_Escape) {
exit(0);
}
/* TODO: handle all of these? */
printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym));
printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym));
printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym));
printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym));
printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
return 1;
printf("sym = %c (%d)\n", sym, sym);
/* convert the keysym to UCS */
uint16_t ucs = keysym2ucs(sym);
if ((int16_t)ucs == -1) {
fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
return 1;
}
/* store the UCS into a string */
uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
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);
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);
input_position++;
if (input_position == limit)
finish_input();
handle_expose(NULL, conn, NULL);
return 1;
}
int main(int argc, char *argv[]) {
char *socket_path = "/tmp/i3-ipc.sock";
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"version", no_argument, 0, 'v'},
{"limit", required_argument, 0, 'l'},
{"prompt", required_argument, 0, 'P'},
{"prefix", required_argument, 0, 'p'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
char *options_string = "s:p:P:l:vh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) {
case 's':
socket_path = strdup(optarg);
break;
case 'v':
printf("i3-input " I3_VERSION);
return 0;
case 'p':
command_prefix = strdup(optarg);
break;
case 'l':
limit = atoi(optarg);
break;
case 'P':
prompt = strdup(optarg);
break;
case 'h':
printf("i3-input " I3_VERSION);
printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]\n");
return 0;
}
}
sockfd = connect_ipc(socket_path);
if (prompt != NULL)
prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
int screens;
xcb_connection_t *conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn))
die("Cannot open display\n");
/* Set up event handlers for key press and key release */
xcb_event_handlers_t evenths;
memset(&evenths, 0, sizeof(xcb_event_handlers_t));
xcb_event_handlers_init(conn, &evenths);
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
modeswitchmask = get_mode_switch_mask(conn);
symbols = xcb_key_symbols_alloc(conn);
uint32_t font_id = get_font_id(conn, pattern, &font_height);
/* Open an input window */
win = open_input_window(conn, 500, font_height + 8);
/* Create pixmap */
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
pixmap = xcb_generate_id(conn);
pixmap_gc = 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_single(conn, pixmap_gc, XCB_GC_FONT, 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);
xcb_flush(conn);
xcb_event_wait_for_event_loop(&evenths);
return 0;
}

104
i3-input/ucs2_to_utf8.c Normal file
View File

@ -0,0 +1,104 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <iconv.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 = calloc(buffer_size, 1);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
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");
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 = malloc(buffer_size);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
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");
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
}
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
}

166
i3-input/xcb.c Normal file
View File

@ -0,0 +1,166 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
#include "i3-input.h"
/*
* Convenience-wrapper around xcb_change_gc which saves us declaring a variable
*
*/
void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) {
xcb_change_gc(conn, gc, mask, &value);
}
/*
* Returns the colorpixel to use for the given hex color (think of HTML).
*
* The hex_color has to start with #, for example #FF00FF.
*
* NOTE that get_colorpixel() does _NOT_ check the given color code for validity.
* This has to be done by the caller.
*
*/
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
char strgroups[3][3] = {{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)),
(strtol(strgroups[1], NULL, 16)),
(strtol(strgroups[2], NULL, 16))};
return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2];
}
/*
* Returns the mask for Mode_switch (to be used for looking up keysymbols by
* keycode).
*
*/
uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_reply_t *modmap_r;
xcb_keycode_t *modmap, kc;
xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, XK_Mode_switch);
if (modeswitchcodes == NULL)
return 0;
modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL);
modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
for (int i = 0; i < 8; i++)
for(int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
kc = modmap[i * modmap_r->keycodes_per_modifier + j];
for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
if (*ktest != kc)
continue;
free(modeswitchcodes);
free(modmap_r);
return (1 << i);
}
}
return 0;
}
/*
* Opens the window we use for input/output and maps it
*
*/
xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) {
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
xcb_window_t win = xcb_generate_id(conn);
//xcb_cursor_t cursor_id = xcb_generate_id(conn);
#if 0
/* Use the default cursor (left pointer) */
if (cursor > -1) {
i3Font *cursor_font = load_font(conn, "cursor");
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
0, 0, 0, 65535, 65535, 65535);
}
#endif
uint32_t mask = 0;
uint32_t values[3];
mask |= XCB_CW_BACK_PIXEL;
values[0] = 0;
mask |= XCB_CW_OVERRIDE_REDIRECT;
values[1] = 1;
mask |= XCB_CW_EVENT_MASK;
values[2] = XCB_EVENT_MASK_EXPOSURE;
xcb_create_window(conn,
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
50, 50, width, height, /* dimensions */
0, /* border = 0, we draw our own */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
mask,
values);
#if 0
if (cursor > -1)
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
#endif
/* Map the window (= make it visible) */
xcb_map_window(conn, win);
return win;
}
/*
* Returns the ID of the font matching the given pattern and stores the height
* of the font (in pixels) in *font_height. die()s if no font matches.
*
*/
int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) {
xcb_void_cookie_t font_cookie;
xcb_list_fonts_with_info_cookie_t info_cookie;
/* Send all our requests first */
int result;
result = xcb_generate_id(conn);
font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern);
info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern);
xcb_generic_error_t *error = xcb_request_check(conn, font_cookie);
if (error != NULL) {
fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code);
exit(1);
}
/* Get information (height/name) for this font */
xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL);
if (reply == NULL)
die("Could not load font \"%s\"\n", pattern);
*font_height = reply->font_ascent + reply->font_descent;
return result;
}

View File

@ -18,6 +18,9 @@ bind Mod1+41 f
# Stacking (Mod1+h)
bind Mod1+43 s
# Tabbed (Mod1+w)
bind Mod1+25 T
# Default (Mod1+e)
bind Mod1+26 d
@ -33,24 +36,44 @@ bind Mod1+44 h
bind Mod1+45 j
bind Mod1+46 k
bind Mod1+47 l
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Left h
bindsym Mod1+Down j
bindsym Mod1+Up k
bindsym Mod1+Right l
# Focus Container (Mod3+j/k/l/;)
bind Mod3+44 wch
bind Mod3+45 wcj
bind Mod3+46 wck
bind Mod3+47 wcl
# (alternatively, you can use the cursor keys:)
bindsym Mod3+Left h
bindsym Mod3+Down j
bindsym Mod3+Up k
bindsym Mod3+Right l
# Snap (Mod1+Control+j/k/l/;)
bind Mod1+Control+44 sh
bind Mod1+Control+45 sj
bind Mod1+Control+46 sk
bind Mod1+Control+47 sl
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Control+Left h
bindsym Mod1+Control+Down j
bindsym Mod1+Control+Up k
bindsym Mod1+Control+Right l
# Move (Mod1+Shift+j/k/l/;)
bind Mod1+Shift+44 mh
bind Mod1+Shift+45 mj
bind Mod1+Shift+46 mk
bind Mod1+Shift+47 ml
# (alternatively, you can use the cursor keys:)
bindsym Mod1+Shift+Left h
bindsym Mod1+Shift+Down j
bindsym Mod1+Shift+Up k
bindsym Mod1+Shift+Right l
# Move Container (Mod3+Shift+j/k/l/;)
bind Mod3+Shift+44 wcmh
@ -97,3 +120,8 @@ bind Mod1+Shift+26 exit
# Mod1+Shift+r restarts i3 inplace
bind Mod1+Shift+27 restart
#############################################################
# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
#############################################################
exec xmessage -file /etc/i3/welcome

43
i3.welcome Normal file
View File

@ -0,0 +1,43 @@
1.) Welcome to i3!
This message provides you with an overview of the default keybindings to use i3.
Please also make sure to have a look at the man page and the user's guide:
http://i3.zekjur.net/docs/userguide.html
2.) Configuration Files
/etc/i3/config is the default configuration. It is recommended to copy it and
afterwards edit it to suit your needs (you can especially disable this message):
cp /etc/i3/config ~/.i3/config
3.) Keybindings
The following explanation is related to the QWERTY layout, but as the default
configuration uses keycodes instead of keysymbols for binding, you still have
to press the same keys, regardless of your keyboard layout.
The Mod1 key is usually bound to the "Alt" key on your keyboard.
Mod1+Enter opens a terminal emulator
Mod1+v starts dmenu (an application launcher)
The directional keys are j(left), k(down), l(up) and ;(right). You can also use
the arrow keys on your keyboard, if you prefer them.
Mod1+<directional key> moves the focus to the window in the given direction
Mod1+Shift+<directional key> moves the window to the given direction,
Mod1+<number> opens the corresponding workspace
Mod1+Shift+<number> moves a window to the wished workspace
Mod1+h sets the mode of a container to stacking
Mod1+e sets the mode back to default
Mod1+f toggles fullscreen mode for the current window
Mod1+Shift+Space toggles floating mode for the current window
Mod1+Shift+q closes a window
Mod1+Shift+r restarts i3 in-place (you will lose your layout, though)
Mod1+Shift+e exits i3
If you have any questions, please don't hesitate to ask!
Have fun using i3

16
include/click.h Normal file
View File

@ -0,0 +1,16 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
*/
#ifndef _CLICK_H
#define _CLICK_H
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event);
#endif

View File

@ -85,6 +85,14 @@ bool client_is_floating(Client *client);
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type);
/**
* Unmap the client, correctly setting any state which is needed.
*
@ -97,6 +105,13 @@ void client_unmap(xcb_connection_t *conn, Client *client);
*/
void client_map(xcb_connection_t *conn, Client *client);
/**
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
/**
* Pretty-prints the clients information into the logfile.
*

View File

@ -17,9 +17,12 @@
#include <stdbool.h>
#include "queue.h"
#include "i3.h"
typedef struct Config Config;
extern Config config;
extern bool config_use_lexer;
extern SLIST_HEAD(modes_head, Mode) modes;
/**
* Part of the struct Config. It makes sense to group colors for background,
@ -40,10 +43,24 @@ struct Colortriple {
struct Variable {
char *key;
char *value;
char *next_match;
SLIST_ENTRY(Variable) variables;
};
/**
* The configuration file can contain multiple sets of bindings. Apart from the
* default set (name == "default"), you can specify other sets and change the
* currently active set of bindings by using the "mode <name>" command.
*
*/
struct Mode {
char *name;
struct bindings_head *bindings;
SLIST_ENTRY(Mode) modes;
};
/**
* Holds part of the configuration (the part which is not already in dedicated
* structures in include/data.h).
@ -55,6 +72,12 @@ struct Config {
const char *ipc_socket_path;
int container_mode;
int container_stack_limit;
int container_stack_limit_value;
const char *default_border;
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
uint32_t floating_modifier;
@ -64,10 +87,12 @@ struct Config {
struct Colortriple focused;
struct Colortriple focused_inactive;
struct Colortriple unfocused;
struct Colortriple urgent;
} client;
struct config_bar {
struct Colortriple focused;
struct Colortriple unfocused;
struct Colortriple urgent;
} bar;
};
@ -93,4 +118,10 @@ void ungrab_all_keys(xcb_connection_t *conn);
*/
void grab_all_keys(xcb_connection_t *conn);
/**
* Switches the key bindings to the given mode, if the mode exists
*
*/
void switch_mode(xcb_connection_t *conn, const char *new_mode);
#endif

View File

@ -201,6 +201,9 @@ struct Workspace {
/** Temporary flag needed for re-querying xinerama screens */
bool reassigned;
/** True if any client on this workspace has its urgent flag set */
bool urgent;
/** the client who is started in fullscreen mode on this workspace,
* NULL if there is none */
Client *fullscreen_client;
@ -229,6 +232,8 @@ struct Workspace {
* opened, for example) have the same size as always */
float *width_factor;
float *height_factor;
TAILQ_ENTRY(Workspace) workspaces;
};
/**
@ -349,6 +354,9 @@ struct Client {
int base_height;
int base_width;
/** The amount of pixels which X will draw around the client. */
int border_width;
/** contains the minimum increment size as specified for the window
* (in pixels). */
int width_increment;
@ -373,6 +381,9 @@ struct Client {
/** Holds the WM_CLASS, useful for matching the client in commands */
char *window_class;
/** Holds the clients mark, for vim-like jumping */
char *mark;
/** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */
xcb_window_t leader;
@ -400,6 +411,9 @@ struct Client {
* the screen and its requested size is used */
bool dock;
/** True if the client set the urgency flag in its WM_HINTS property */
bool urgent;
/* After leaving fullscreen mode, a client needs to be reconfigured
* (configuration = setting X, Y, width and height). By setting the
* force_reconfigure flag, render_layout() will reconfigure the
@ -427,8 +441,8 @@ struct Client {
};
/**
* A container is either in default or stacking mode. It sits inside each cell
* of the table.
* A container is either in default, stacking or tabbed mode. There is one for
* each cell of the table.
*
*/
struct Container {
@ -456,7 +470,16 @@ struct Container {
/* Ensure MODE_DEFAULT maps to 0 because we use calloc for
* initialization later */
enum { MODE_DEFAULT = 0, MODE_STACK } mode;
enum { MODE_DEFAULT = 0, MODE_STACK, MODE_TABBED } mode;
/* When in stacking, one can either have unlimited windows inside the
* container or set a limit for the rows or columns the stack window
* should display to use the screen more efficiently. */
enum { STACK_LIMIT_NONE = 0, STACK_LIMIT_COLS, STACK_LIMIT_ROWS } stack_limit;
/* The number of columns or rows to limit to, see stack_limit */
int stack_limit_value;
CIRCLEQ_HEAD(client_head, Client) clients;
};
@ -471,7 +494,7 @@ struct Screen {
int num;
/** Current workspace selected on this virtual screen */
int current_workspace;
Workspace *current_workspace;
/** x, y, width, height */
Rect rect;

View File

@ -55,6 +55,15 @@ int floating_border_click(xcb_connection_t *conn, Client *client,
void floating_drag_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
/**
* Called when the user right-clicked on the titlebar of a floating window to
* resize it.
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client,
xcb_button_press_event_t *event);
/**
* Changes focus in the given direction for floating clients.
*

View File

@ -160,6 +160,13 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t name,
xcb_get_property_reply_t *reply);
/**
* Handles the WM_HINTS property for extracting the urgency state of the window.
*
*/
int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply);
/**
* Handles the transient for hints set by a window, signalizing that this
* window is a popup window for some other window.

View File

@ -27,7 +27,7 @@ extern xcb_connection_t *global_conn;
extern xcb_key_symbols_t *keysyms;
extern char **start_argv;
extern Display *xkbdpy;
extern TAILQ_HEAD(bindings_head, Binding) bindings;
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(assignments_head, Assignment) assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;

View File

@ -22,6 +22,9 @@
*/
int get_unoccupied_x(Workspace *workspace);
/** See get_unoccupied_x */
int get_unoccupied_y(Workspace *workspace);
/**
* (Re-)draws window decorations for a given Client onto the given
* drawable/graphic context. When in stacking mode, the window decorations
@ -29,7 +32,8 @@ int get_unoccupied_x(Workspace *workspace);
*
*/
void decorate_window(xcb_connection_t *conn, Client *client,
xcb_drawable_t drawable, xcb_gcontext_t gc, int offset);
xcb_drawable_t drawable, xcb_gcontext_t gc,
int offset_x, int offset_y);
/**
* Redecorates the given client correctly by checking if its in a stacking

View File

@ -42,6 +42,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
*/
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height);
int16_t x, int16_t y, uint16_t width, uint16_t height,
uint32_t border_width);
#endif

View File

@ -24,5 +24,13 @@ typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t;
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first,
int second, resize_orientation_t orientation,
xcb_button_press_event_t *event);
/**
* Resizes a column/row by the given amount of pixels. Called by
* resize_graphical_handler (the user clicked) or parse_resize_command (the
* user issued the command)
*
*/
void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, int pixels);
#endif

View File

@ -21,7 +21,8 @@
#define CUR_CELL (CUR_TABLE[current_col][current_row])
extern Workspace *c_ws;
extern Workspace workspaces[10];
extern TAILQ_HEAD(workspaces_head, Workspace) *workspaces;
//extern int num_workspaces;
extern int current_col;
extern int current_row;

View File

@ -161,4 +161,9 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
Client *get_matching_client(xcb_connection_t *conn,
const char *window_classtitle, Client *specific);
#if defined(__OpenBSD__)
/* OpenBSD does not provide memmem(), so we provide FreeBSDs implementation */
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
#endif
#endif

View File

@ -16,6 +16,14 @@
#ifndef _WORKSPACE_H
#define _WORKSPACE_H
/**
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
* memory and initializing the data structures correctly).
*
*/
Workspace *workspace_get(int number);
/**
* Sets the name (or just its number) for the given workspace. This has to
* be called for every workspace as the rendering function
@ -62,7 +70,17 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
*/
void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws);
/**
* Maps all clients (and stack windows) of the given workspace.
*
*/
void workspace_map_clients(xcb_connection_t *conn, Workspace *ws);
/**
* Goes through all clients on the given workspace and updates the workspaces
* urgent flag accordingly.
*
*/
void workspace_update_urgent_flag(Workspace *ws);
#endif

View File

@ -1,4 +1,6 @@
all:
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man
clean:
rm -f i3.xml i3.1 i3.html
rm -f i3.{1,html,xml} i3-msg.{1,html,xml} i3-input.{1,html,xml}

View File

@ -7,7 +7,7 @@ template::[header-declarations]
<refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo>
<refmiscinfo class="version">beta</refmiscinfo>
<refmiscinfo class="version">delta</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta>
<refnamediv>

31
man/i3-input.man Normal file
View File

@ -0,0 +1,31 @@
i3-input(1)
=========
Michael Stapelberg <michael+i3@stapelberg.de>
v3.delta, November 2009
== NAME
i3-input - interactively take a command for i3
== SYNOPSIS
i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]
== DESCRIPTION
i3-input is a tool to take commands (or parts of a command) and then send it
to i3. This is useful for example for the mark/goto command.
== EXAMPLE
------------------------------------------------
i3-input -p 'mark ' -l 1 -P 'Mark: '
------------------------------------------------
== SEE ALSO
i3(1)
== AUTHOR
Michael Stapelberg and contributors

33
man/i3-msg.man Normal file
View File

@ -0,0 +1,33 @@
i3-msg(1)
=========
Michael Stapelberg <michael+i3@stapelberg.de>
v3.delta, November 2009
== NAME
i3-msg - send messages to i3
== SYNOPSIS
i3-msg "message"
== DESCRIPTION
i3-msg is a sample implementation for a client using the unix socket IPC
interface to i3. At the moment, it can only be used for sending commands
(like in configuration file for key bindings), but this may change in the
future (staying backwards-compatible, of course).
== EXAMPLE
------------------------------------------------
i3-msg "bp" # Use 1-px border for current client
------------------------------------------------
== SEE ALSO
i3(1)
== AUTHOR
Michael Stapelberg and contributors

View File

@ -1,7 +1,7 @@
i3(1)
=====
Michael Stapelberg <michael+i3@stapelberg.de>
v3.gamma, August 2009
v3.delta, November 2009
== NAME
@ -74,7 +74,8 @@ two virtual screens.
Here is a short overview of the default keybindings:
j/k/l/;::
Direction keys (left, down, up, right). They are on your homerow (see the mark on your "j" key).
Direction keys (left, down, up, right). They are on your homerow (see the mark
on your "j" key). Alternatively, you can use the cursor keys.
Mod1+<direction>::
Focus window in <direction>.

92
src/cfgparse.l Normal file
View File

@ -0,0 +1,92 @@
%option nounput
%option noinput
%{
/*
* vim:ts=8:expandtab
*
*/
#include <stdio.h>
#include <string.h>
#include "cfgparse.tab.h"
#include <xcb/xcb.h>
#include "data.h"
#include "config.h"
%}
%Start BIND_COND
%Start BINDSYM_COND
%Start BIND_AWS_COND
%Start BINDSYM_AWS_COND
%Start BIND_A2WS_COND
%Start ASSIGN_COND
%Start COLOR_COND
%Start SCREEN_COND
%Start SCREEN_AWS_COND
%%
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
mode { return TOKMODE; }
bind { BEGIN(BIND_COND); return TOKBIND; }
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
floating_modifier { return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
screen { BEGIN(SCREEN_COND); return TOKSCREEN; }
terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
font { BEGIN(BIND_AWS_COND); return TOKFONT; }
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; }
set[^\n]* { return TOKCOMMENT; }
ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
new_container { return TOKNEWCONTAINER; }
new_window { return TOKNEWWINDOW; }
default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; }
stack-limit { return TOKSTACKLIMIT; }
cols { yylval.number = STACK_LIMIT_COLS; return TOKSTACKLIMIT; }
rows { yylval.number = STACK_LIMIT_ROWS; return TOKSTACKLIMIT; }
exec { BEGIN(BIND_AWS_COND); return TOKEXEC; }
client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; }
client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; }
client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; }
client.urgent { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; }
bar.focused { BEGIN(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; }
bar.unfocused { BEGIN(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; }
bar.urgent { BEGIN(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; }
Mod1 { yylval.number = BIND_MOD1; return MODIFIER; }
Mod2 { yylval.number = BIND_MOD2; return MODIFIER; }
Mod3 { yylval.number = BIND_MOD3; return MODIFIER; }
Mod4 { yylval.number = BIND_MOD4; return MODIFIER; }
Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
control { return TOKCONTROL; }
shift { return TOKSHIFT; }
→ { return TOKARROW; }
\n /* ignore end of line */;
<SCREEN_AWS_COND>x { return (int)yytext[0]; }
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<BINDSYM_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<SCREEN_COND>[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; }
<SCREEN_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; }
\"[^\"]+\" {
/* if ASSIGN_COND then */
BEGIN(INITIAL);
/* yylval will be the string, but without quotes */
char *copy = strdup(yytext+1);
copy[strlen(copy)-1] = '\0';
yylval.string = copy;
return QUOTEDSTRING;
}
<ASSIGN_COND>[^ \t]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; }
<BINDSYM_AWS_COND>[a-zA-Z0-9]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
. { return (int)yytext[0]; }
%%

537
src/cfgparse.y Normal file
View File

@ -0,0 +1,537 @@
%{
/*
* vim:ts=8:expandtab
*
*/
#include <stdio.h>
#include <string.h>
#include <xcb/xcb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include "data.h"
#include "config.h"
#include "i3.h"
#include "util.h"
#include "queue.h"
#include "table.h"
#include "workspace.h"
#include "xcb.h"
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(void);
extern int yyparse(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
static struct bindings_head *current_bindings;
int yydebug = 1;
void yyerror(const char *str) {
fprintf(stderr,"error: %s\n",str);
}
int yywrap() {
return 1;
}
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
struct stat stbuf;
char *buf;
FILE *fstr;
char buffer[1026], key[512], value[512];
if ((fd = open(f, O_RDONLY)) == -1)
die("Could not open configuration file: %s\n", strerror(errno));
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
buf = smalloc(stbuf.st_size * sizeof(char));
while (read_bytes < stbuf.st_size) {
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
die("Could not read(): %s\n", strerror(errno));
read_bytes += ret;
}
if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
die("Could not lseek: %s\n", strerror(errno));
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
while (!feof(fstr)) {
if (fgets(buffer, 1024, fstr) == NULL) {
if (feof(fstr))
break;
die("Could not read configuration file\n");
}
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
key[0] == '#' || strlen(key) < 3)
continue;
if (strcasecmp(key, "set") == 0) {
if (value[0] != '$')
die("Malformed variable assignment, name has to start with $\n");
/* get key/value for this variable */
char *v_key = value, *v_value;
if ((v_value = strstr(value, " ")) == NULL)
die("Malformed variable assignment, need a value\n");
*(v_value++) = '\0';
struct Variable *new = scalloc(sizeof(struct Variable));
new->key = sstrdup(v_key);
new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables);
LOG("Got new variable %s = %s\n", v_key, v_value);
continue;
}
}
/* For every custom variable, see how often it occurs in the file and
* how much extra bytes it requires when replaced. */
struct Variable *current, *nearest;
int extra_bytes = 0;
SLIST_FOREACH(current, &variables, variables) {
int extra = (strlen(current->value) - strlen(current->key));
char *next;
for (next = buf;
(next = strcasestr(buf + (next - buf), current->key)) != NULL;
next += strlen(current->key))
extra_bytes += extra;
}
/* Then, allocate a new buffer and copy the file over to the new one,
* but replace occurences of our variables */
char *walk = buf, *destwalk;
char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
destwalk = new;
while (walk < (buf + stbuf.st_size)) {
/* Find the next variable */
SLIST_FOREACH(current, &variables, variables)
current->next_match = strcasestr(walk, current->key);
nearest = NULL;
int distance = stbuf.st_size;
SLIST_FOREACH(current, &variables, variables) {
if (current->next_match == NULL)
continue;
if ((current->next_match - walk) < distance) {
distance = (current->next_match - walk);
nearest = current;
}
}
if (nearest == NULL) {
/* If there are no more variables, we just copy the rest */
strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
destwalk += (buf + stbuf.st_size) - walk;
*destwalk = '\0';
break;
} else {
/* Copy until the next variable, then copy its value */
strncpy(destwalk, walk, distance);
strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
walk += distance + strlen(nearest->key);
destwalk += distance + strlen(nearest->value);
}
}
yy_scan_string(new);
if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n");
exit(1);
}
free(new);
free(buf);
}
%}
%expect 1
%union {
int number;
char *string;
struct Colortriple *color;
struct Assignment *assignment;
struct Binding *binding;
}
%token <number>NUMBER
%token <string>WORD
%token <string>STR
%token <string>STR_NG
%token <string>HEX
%token TOKBIND
%token TOKTERMINAL
%token TOKCOMMENT
%token TOKFONT
%token TOKBINDSYM
%token MODIFIER
%token TOKCONTROL
%token TOKSHIFT
%token WHITESPACE
%token TOKFLOATING_MODIFIER
%token QUOTEDSTRING
%token TOKWORKSPACE
%token TOKSCREEN
%token TOKASSIGN
%token TOKSET
%token TOKIPCSOCKET
%token TOKEXEC
%token TOKCOLOR
%token TOKARROW
%token TOKMODE
%token TOKNEWCONTAINER
%token TOKNEWWINDOW
%token TOKCONTAINERMODE
%token TOKSTACKLIMIT
%%
lines: /* empty */
| lines WHITESPACE line
| lines line
;
line:
bindline
| mode
| floating_modifier
| new_container
| new_window
| workspace
| assign
| ipcsocket
| exec
| color
| terminal
| font
| comment
;
comment:
TOKCOMMENT
;
command:
STR
;
bindline:
binding
{
TAILQ_INSERT_TAIL(bindings, $<binding>1, bindings);
}
;
binding:
TOKBIND WHITESPACE bind { $<binding>$ = $<binding>3; }
| TOKBINDSYM WHITESPACE bindsym { $<binding>$ = $<binding>3; }
;
bind:
binding_modifiers NUMBER WHITESPACE command
{
printf("\tFound binding mod%d with key %d and command %s\n", $<number>1, $2, $<string>4);
Binding *new = scalloc(sizeof(Binding));
new->keycode = $<number>2;
new->mods = $<number>1;
new->command = sstrdup($<string>4);
$<binding>$ = new;
}
;
bindsym:
binding_modifiers word_or_number WHITESPACE command
{
printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
Binding *new = scalloc(sizeof(Binding));
new->symbol = sstrdup($<string>2);
new->mods = $<number>1;
new->command = sstrdup($<string>4);
$<binding>$ = new;
}
;
word_or_number:
WORD
| NUMBER
{
asprintf(&$<string>$, "%d", $1);
}
;
mode:
TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}'
{
if (strcasecmp($<string>3, "default") == 0) {
printf("You cannot use the name \"default\" for your mode\n");
exit(1);
}
printf("\t now in mode %s\n", $<string>3);
printf("\t current bindings = %p\n", current_bindings);
Binding *binding;
TAILQ_FOREACH(binding, current_bindings, bindings) {
printf("got binding on mods %d, keycode %d, symbol %s, command %s\n",
binding->mods, binding->keycode, binding->symbol, binding->command);
}
struct Mode *mode = scalloc(sizeof(struct Mode));
mode->name = strdup($<string>3);
mode->bindings = current_bindings;
current_bindings = NULL;
SLIST_INSERT_HEAD(&modes, mode, modes);
}
;
modelines:
/* empty */
| modelines modeline
;
modeline:
WHITESPACE
| comment
| binding
{
if (current_bindings == NULL) {
current_bindings = scalloc(sizeof(struct bindings_head));
TAILQ_INIT(current_bindings);
}
TAILQ_INSERT_TAIL(current_bindings, $<binding>1, bindings);
}
;
floating_modifier:
TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
{
LOG("floating modifier = %d\n", $<number>3);
config.floating_modifier = $<number>3;
}
;
new_container:
TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE
{
LOG("new containers will be in mode %d\n", $<number>3);
config.container_mode = $<number>3;
/* We also need to change the layout of the already existing
* workspaces here. Workspaces may exist at this point because
* of the other directives which are modifying workspaces
* (setting the preferred screen or name). While the workspace
* objects are already created, they have never been used.
* Thus, the user very likely awaits the default container mode
* to trigger in this case, regardless of where it is inside
* his configuration file. */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->table == NULL)
continue;
switch_layout_mode(global_conn,
ws->table[0][0],
config.container_mode);
}
}
| TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
{
LOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
config.container_stack_limit = $<number>5;
config.container_stack_limit_value = $<number>7;
/* See the comment above */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->table == NULL)
continue;
Container *con = ws->table[0][0];
con->stack_limit = config.container_stack_limit;
con->stack_limit_value = config.container_stack_limit_value;
}
}
;
new_window:
TOKNEWWINDOW WHITESPACE WORD
{
LOG("new windows should start in mode %s\n", $<string>3);
config.default_border = strdup($<string>3);
}
;
workspace:
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
Workspace *ws = workspace_get(ws_num - 1);
ws->preferred_screen = sstrdup($<string>7);
if ($<string>8 != NULL)
workspace_set_name(ws, $<string>8);
}
}
| TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
{
int ws_num = $<number>3;
if (ws_num < 1) {
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
} else {
if ($<string>5 != NULL)
workspace_set_name(workspace_get(ws_num - 1), $<string>5);
}
}
;
optional_workspace_name:
/* empty */ { $<string>$ = NULL; }
| WHITESPACE workspace_name { $<string>$ = $<string>2; }
;
workspace_name:
QUOTEDSTRING { $<string>$ = $<string>1; }
| STR { $<string>$ = $<string>1; }
;
screen:
NUMBER { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' { asprintf(&$<string>$, "%d", $<number>1); }
| NUMBER 'x' NUMBER { asprintf(&$<string>$, "%dx%d", $<number>1, $<number>3); }
| 'x' NUMBER { asprintf(&$<string>$, "x%d", $<number>2); }
;
assign:
TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
{
printf("assignment of %s\n", $<string>3);
struct Assignment *new = $<assignment>6;
printf(" to %d\n", new->workspace);
printf(" floating = %d\n", new->floating);
new->windowclass_title = strdup($<string>3);
TAILQ_INSERT_TAIL(&assignments, new, assignments);
}
;
assign_target:
NUMBER
{
struct Assignment *new = scalloc(sizeof(struct Assignment));
new->workspace = $<number>1;
new->floating = ASSIGN_FLOATING_NO;
$<assignment>$ = new;
}
| '~'
{
struct Assignment *new = scalloc(sizeof(struct Assignment));
new->floating = ASSIGN_FLOATING_ONLY;
$<assignment>$ = new;
}
| '~' NUMBER
{
struct Assignment *new = scalloc(sizeof(struct Assignment));
new->workspace = $<number>2;
new->floating = ASSIGN_FLOATING;
$<assignment>$ = new;
}
;
window_class:
QUOTEDSTRING
| STR_NG
;
optional_arrow:
/* NULL */
| TOKARROW WHITESPACE
;
ipcsocket:
TOKIPCSOCKET WHITESPACE STR
{
config.ipc_socket_path = sstrdup($<string>3);
}
;
exec:
TOKEXEC WHITESPACE STR
{
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup($<string>3);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
}
;
terminal:
TOKTERMINAL WHITESPACE STR
{
config.terminal = sstrdup($<string>3);
printf("terminal %s\n", config.terminal);
}
;
font:
TOKFONT WHITESPACE STR
{
config.font = sstrdup($<string>3);
printf("font %s\n", config.font);
}
;
color:
TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel
{
struct Colortriple *dest = $<color>1;
dest->border = $<number>3;
dest->background = $<number>5;
dest->text = $<number>7;
}
;
colorpixel:
'#' HEX
{
char *hex;
if (asprintf(&hex, "#%s", $<string>2) == -1)
die("asprintf()");
$<number>$ = get_colorpixel(global_conn, hex);
free(hex);
}
;
binding_modifiers:
/* NULL */ { $<number>$ = 0; }
| binding_modifier
| binding_modifiers '+' binding_modifier { $<number>$ = $<number>1 | $<number>3; }
| binding_modifiers '+' { $<number>$ = $<number>1; }
;
binding_modifier:
MODIFIER { $<number>$ = $<number>1; }
| TOKCONTROL { $<number>$ = BIND_CONTROL; }
| TOKSHIFT { $<number>$ = BIND_SHIFT; }
;

385
src/click.c Normal file
View File

@ -0,0 +1,385 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* src/click.c: Contains the handlers for button press (mouse click) events
* because they are quite large.
*
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#include <X11/XKBlib.h>
#include "i3.h"
#include "queue.h"
#include "table.h"
#include "config.h"
#include "util.h"
#include "xcb.h"
#include "client.h"
#include "workspace.h"
#include "commands.h"
#include "floating.h"
#include "resize.h"
static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
struct Stack_Window *current;
SLIST_FOREACH(current, &stack_wins, stack_windows) {
if (current->window != window_id)
continue;
return current;
}
return NULL;
}
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
*
*/
static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
struct Stack_Window *stack_win;
/* If we find a corresponding stack window, we can handle the event */
if ((stack_win = get_stack_window(event->event)) == NULL)
return false;
/* A stack window was clicked, we check if it was button4 or button5
which are scroll up / scroll down. */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN);
focus_window_in_container(conn, CUR_CELL, direction);
return true;
}
/* It was no scrolling, so we calculate the destination client by
dividing the Y position of the event through the height of a window
decoration and then set the focus to this client. */
i3Font *font = load_font(conn, config.font);
int decoration_height = (font->height + 2 + 2);
int destination = (event->event_y / decoration_height),
c = 0,
num_clients = 0;
Client *client;
Container *container = stack_win->container;
CIRCLEQ_FOREACH(client, &(container->clients), clients)
num_clients++;
/* If we dont have any clients in this container, we cannot do
* anything useful anyways. */
if (num_clients == 0)
return true;
if (container->mode == MODE_TABBED)
destination = (event->event_x / (container->width / num_clients));
else if (container->mode == MODE_STACK &&
container->stack_limit != STACK_LIMIT_NONE) {
if (container->stack_limit == STACK_LIMIT_COLS) {
int wrap = ceil((float)num_clients / container->stack_limit_value);
int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (wrap * clicked_column) + clicked_row;
} else {
int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
int clicked_column = (event->event_x / width);
int clicked_row = (event->event_y / decoration_height);
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
destination = (container->stack_limit_value * clicked_column) + clicked_row;
}
}
LOG("Click on stack_win for client %d\n", destination);
CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
if (c++ == destination) {
set_focus(conn, client, true);
return true;
}
return true;
}
/*
* Checks if the button press was on a bar, switches to the workspace and returns true
* if so, or false otherwise.
*
*/
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
if (screen->bar != event->event)
continue;
LOG("Click on a bar\n");
/* Check if the button was one of button4 or button5 (scroll up / scroll down) */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
Workspace *ws = c_ws;
if (event->detail == XCB_BUTTON_INDEX_5) {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == screen) {
workspace_show(conn, ws->num + 1);
return true;
}
}
} else {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == screen) {
workspace_show(conn, ws->num + 1);
return true;
}
}
}
return true;
}
int drawn = 0;
/* Because workspaces can be on different screens, we need to loop
through all of them and decide to count it based on its ->screen */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
continue;
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
ws->num, drawn, ws->text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
workspace_show(conn, ws->num + 1);
return true;
}
drawn += ws->text_width + 5 + 5 + 2;
}
return true;
}
return false;
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("Button %d pressed\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
if (client == NULL) {
client = table_get(&by_parent, event->event);
border_click = true;
}
/* See if this was a click with the configured modifier. If so, we need
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
if (client == NULL) {
LOG("Not handling, floating_modifier was pressed and no client found\n");
return 1;
}
if (client->fullscreen) {
LOG("Not handling, client is in fullscreen mode\n");
return 1;
}
if (client_is_floating(client)) {
LOG("button %d pressed\n", event->detail);
if (event->detail == 1) {
LOG("left mouse button, dragging\n");
floating_drag_window(conn, client, event);
} else if (event->detail == 3) {
LOG("right mouse button\n");
floating_resize_window(conn, client, event);
}
return 1;
} else {
/* The client is in tiling layout. We can still
* initiate a resize with the right mouse button,
* by chosing the border which is the most near one
* to the position of the mouse pointer */
if (event->detail == 3) {
int to_right = client->rect.width - event->event_x,
to_left = event->event_x,
to_top = event->event_y,
to_bottom = client->rect.height - event->event_y;
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
Workspace *ws = con->workspace;
int first = 0, second = 0;
LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
to_right, to_left, to_top, to_bottom);
if (to_right < to_left &&
to_right < to_top &&
to_right < to_bottom) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
if (!cell_exists(first, con->row) ||
(first == (ws->cols-1)))
return 1;
second = first + 1;
} else if (to_left < to_right &&
to_left < to_top &&
to_left < to_bottom) {
/* …left border */
if (con->col == 0)
return 1;
first = con->col - 1;
second = con->col;
} else if (to_top < to_right &&
to_top < to_left &&
to_top < to_bottom) {
/* This was a press on the top border */
if (con->row == 0)
return 1;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
} else if (to_bottom < to_right &&
to_bottom < to_left &&
to_bottom < to_top) {
/* …bottom border */
first = con->row + (con->rowspan - 1);
if (!cell_exists(con->col, first) ||
(first == (ws->rows-1)))
return 1;
second = first + 1;
orientation = O_HORIZONTAL;
}
return resize_graphical_handler(conn, ws, first, second, orientation, event);
}
}
}
if (client == NULL) {
/* The client was neither on a clients titlebar nor on a client itself, maybe on a stack_window? */
if (button_press_stackwin(conn, event))
return 1;
/* Or on a bar? */
if (button_press_bar(conn, event))
return 1;
LOG("Could not handle this button press\n");
return 1;
}
/* Set focus in any case */
set_focus(conn, client, true);
/* Lets see if this was on the borders (= resize). If not, were done */
LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
int first, second;
if (client->dock) {
LOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
/* Some clients (xfontsel for example) seem to pass clicks on their
* window to the parent window, thus we receive an event here which in
* reality is a border_click. Check for the position and fix state. */
if (border_click &&
event->event_x >= client->child_rect.x &&
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
LOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
return 1;
}
/* Dont handle events inside the titlebar, only borders are interesting */
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1;
}
if (client_is_floating(client))
return floating_border_click(conn, client, event);
Workspace *ws = con->workspace;
if (event->event_y < 2) {
/* This was a press on the top border */
if (con->row == 0)
return 1;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
} else if (event->event_y >= (client->rect.height - 2)) {
/* …bottom border */
first = con->row + (con->rowspan - 1);
if (!cell_exists(con->col, first) ||
(first == (ws->rows-1)))
return 1;
second = first + 1;
orientation = O_HORIZONTAL;
} else if (event->event_x <= 2) {
/* …left border */
if (con->col == 0)
return 1;
first = con->col - 1;
second = con->col;
} else if (event->event_x > 2) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
if (!cell_exists(first, con->row) ||
(first == (ws->cols-1)))
return 1;
second = first + 1;
}
return resize_graphical_handler(conn, ws, first, second, orientation, event);
}

View File

@ -24,6 +24,8 @@
#include "queue.h"
#include "layout.h"
#include "client.h"
#include "table.h"
#include "workspace.h"
/*
* Removes the given client from the container, either because it will be inserted into another
@ -38,7 +40,9 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) {
LOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
@ -233,8 +237,9 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
*/
void client_set_below_floating(xcb_connection_t *conn, Client *client) {
/* Ensure that it is below all floating clients */
Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients));
if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) {
Workspace *ws = client->workspace;
Client *first_floating = TAILQ_FIRST(&(ws->floating_clients));
if (first_floating != TAILQ_END(&(ws->floating_clients))) {
LOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
@ -253,30 +258,41 @@ bool client_is_floating(Client *client) {
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
* completely borderless (b) without actually re-rendering the layout. Useful
* for calling it when initializing a new client.
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
bool client_init_border(xcb_connection_t *conn, Client *client, char border_type) {
switch (border_type) {
case 'n':
LOG("Changing to normal border\n");
client->titlebar_position = TITLEBAR_TOP;
client->borderless = false;
break;
return true;
case 'p':
LOG("Changing to 1px border\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = false;
break;
return true;
case 'b':
LOG("Changing to borderless\n");
client->titlebar_position = TITLEBAR_OFF;
client->borderless = true;
break;
return true;
default:
LOG("Unknown border mode\n");
return;
return false;
}
}
/*
* Change the border type for the given client to normal (n), 1px border (p) or
* completely borderless (b).
*
*/
void client_change_border(xcb_connection_t *conn, Client *client, char border_type) {
if (!client_init_border(conn, client, border_type))
return;
/* Ensure that the childs position inside our window gets updated */
client->force_reconfigure = true;
@ -314,3 +330,31 @@ void client_map(xcb_connection_t *conn, Client *client) {
xcb_map_window(conn, client->frame);
}
/*
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
if (client->mark != NULL)
free(client->mark);
client->mark = sstrdup(mark);
/* Make sure no other client has this mark set */
Client *current;
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (current == client ||
current->mark == NULL ||
strcmp(current->mark, mark) != 0)
continue;
free(current->mark);
current->mark = NULL;
/* We can break here since there can only be one other
* client with this mark. */
break;
}
}

View File

@ -29,6 +29,7 @@
#include "config.h"
#include "workspace.h"
#include "commands.h"
#include "resize.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -58,6 +59,24 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t;
static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
Client *current;
LOG("Jumping to \"%s\"\n", mark);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (current->mark == NULL || strcmp(current->mark, mark) != 0)
continue;
workspace_show(conn, current->workspace->num + 1);
set_focus(conn, current, true);
return;
}
LOG("No window with this mark found\n");
}
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
LOG("focusing direction %d\n", direction);
@ -113,7 +132,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
}
LOG("Switching to ws %d\n", target->current_workspace + 1);
workspace_show(conn, target->current_workspace + 1);
workspace_show(conn, target->current_workspace->num + 1);
return;
}
@ -148,7 +167,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
/* No screen found? Then wrap */
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
t_ws = screen->current_workspace;
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
}
@ -190,7 +209,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
LOG("Wrapping screen around horizontally\n");
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen);
}
t_ws = &(workspaces[screen->current_workspace]);
t_ws = screen->current_workspace;
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
}
@ -506,7 +525,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
/* t_ws (to workspace) is just a container pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]),
Workspace *t_ws = workspace_get(workspace-1),
*old_ws = client->workspace;
LOG("moving floating\n");
@ -561,7 +580,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
assert(container != NULL);
/* t_ws (to workspace) is just a container pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
Workspace *t_ws = workspace_get(workspace-1);
Client *current_client = container->currently_focused;
if (current_client == NULL) {
@ -767,31 +786,75 @@ static char **append_argument(char **original, char *argument) {
*
*/
static void next_previous_workspace(xcb_connection_t *conn, int direction) {
Workspace *t_ws;
int i;
Workspace *ws = c_ws;
if (direction == 'n') {
/* If we are on the last workspace, we cannot go any further */
if (c_ws->num == 9)
return;
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == NULL)
continue;
for (i = c_ws->num + 1; i <= 9; i++) {
t_ws = &(workspaces[i]);
if (t_ws->screen != NULL)
break;
workspace_show(conn, ws->num + 1);
return;
}
} else if (direction == 'p') {
if (c_ws->num == 0)
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
if (ws->screen == NULL)
continue;
workspace_show(conn, ws->num + 1);
return;
for (i = c_ws->num - 1; i >= 0 ; i--) {
t_ws = &(workspaces[i]);
if (t_ws->screen != NULL)
break;
}
}
}
if (t_ws->screen != NULL)
workspace_show(conn, i+1);
static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, const char *command) {
int first, second;
resize_orientation_t orientation = O_VERTICAL;
Container *con = last_focused->container;
Workspace *ws = con->workspace;
if (STARTS_WITH(command, "left")) {
if (con->col == 0)
return;
first = con->col - 1;
second = con->col;
command += strlen("left");
} else if (STARTS_WITH(command, "right")) {
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
if (!cell_exists(first, con->row) ||
(first == (ws->cols-1)))
return;
second = first + 1;
command += strlen("right");
} else if (STARTS_WITH(command, "top")) {
if (con->row == 0)
return;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
command += strlen("top");
} else if (STARTS_WITH(command, "bottom")) {
first = con->row + (con->rowspan - 1);
if (!cell_exists(con->col, first) ||
(first == (ws->rows-1)))
return;
second = first + 1;
orientation = O_HORIZONTAL;
command += strlen("bottom");
} else {
LOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return;
}
int pixels = atoi(command);
if (pixels == 0)
return;
resize_container(conn, ws, first, second, orientation, pixels);
}
/*
@ -817,6 +880,76 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
if (STARTS_WITH(command, "mark")) {
if (last_focused == NULL) {
LOG("There is no window to mark\n");
return;
}
const char *rest = command + strlen("mark");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive mark starting\n");
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
} else {
LOG("mark with \"%s\"\n", rest);
client_mark(conn, last_focused, rest);
}
return;
}
if (STARTS_WITH(command, "goto")) {
const char *rest = command + strlen("goto");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive go to mark starting\n");
start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
} else {
LOG("go to \"%s\"\n", rest);
jump_to_mark(conn, rest);
}
return;
}
if (STARTS_WITH(command, "stack-limit ")) {
if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("No container focused\n");
return;
}
const char *rest = command + strlen("stack-limit ");
if (strncmp(rest, "rows ", strlen("rows ")) == 0) {
last_focused->container->stack_limit = STACK_LIMIT_ROWS;
rest += strlen("rows ");
} else if (strncmp(rest, "cols ", strlen("cols ")) == 0) {
last_focused->container->stack_limit = STACK_LIMIT_COLS;
rest += strlen("cols ");
} else {
LOG("Syntax: stack-limit <cols|rows> <limit>\n");
return;
}
last_focused->container->stack_limit_value = atoi(rest);
if (last_focused->container->stack_limit_value == 0)
last_focused->container->stack_limit = STACK_LIMIT_NONE;
return;
}
if (STARTS_WITH(command, "resize ")) {
if (last_focused == NULL)
return;
const char *rest = command + strlen("resize ");
parse_resize_command(conn, last_focused, rest);
return;
}
if (STARTS_WITH(command, "mode ")) {
const char *rest = command + strlen("mode ");
switch_mode(conn, rest);
return;
}
/* Is it an <exit>? */
if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n");
@ -875,23 +1008,41 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
/* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
if (last_focused != NULL && client_is_floating(last_focused)) {
LOG("not switching, this is a floating client\n");
return;
}
LOG("Switching mode for current container\n");
switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
int new_mode = MODE_DEFAULT;
if (command[0] == 's')
new_mode = MODE_STACK;
if (command[0] == 'T')
new_mode = MODE_TABBED;
switch_layout_mode(conn, CUR_CELL, new_mode);
return;
}
/* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */
/* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
if (command[0] == 'b') {
if (last_focused == NULL) {
LOG("No window focused, cannot change border type\n");
return;
}
client_change_border(conn, last_focused, command[1]);
char com = command[1];
if (command[1] == 't') {
if (last_focused->titlebar_position == TITLEBAR_TOP &&
!last_focused->borderless)
com = 'p';
else if (last_focused->titlebar_position == TITLEBAR_OFF &&
!last_focused->borderless)
com = 'b';
else com = 'n';
}
client_change_border(conn, last_focused, com);
return;
}
@ -934,14 +1085,16 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return;
}
Workspace *ws = last_focused->workspace;
toggle_floating_mode(conn, last_focused, false);
/* delete all empty columns/rows */
cleanup_table(conn, last_focused->workspace);
cleanup_table(conn, ws);
/* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, last_focused->workspace);
fix_colrowspan(conn, ws);
render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
render_workspace(conn, ws->screen, ws);
/* Re-focus the client because cleanup_table sets the focus to the last
* focused client inside a container only. */

View File

@ -26,7 +26,14 @@
#include "table.h"
#include "workspace.h"
/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely
* switched to the new scanner/parser. */
void parse_file(const char *f);
Config config;
struct modes_head modes;
bool config_use_lexer = false;
/*
* This function resolves ~ in pathnames.
@ -93,7 +100,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint
*/
void grab_all_keys(xcb_connection_t *conn) {
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings) {
TAILQ_FOREACH(bind, bindings, bindings) {
/* The easy case: the user specified a keycode directly. */
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
@ -107,14 +114,23 @@ void grab_all_keys(xcb_connection_t *conn) {
continue;
}
#ifdef OLD_XCB_KEYSYMS_API
bind->number_keycodes = 1;
xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(conn, bind, code);
bind->translated_to = smalloc(sizeof(xcb_keycode_t));
memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
#else
uint32_t last_keycode = 0;
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue;
}
uint32_t last_keycode = 0;
bind->number_keycodes = 0;
for (xcb_keycode_t *walk = keycodes; *walk != 0; walk++) {
/* We hope duplicate keycodes will be returned in order
* and skip them */
@ -128,9 +144,32 @@ void grab_all_keys(xcb_connection_t *conn) {
bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
free(keycodes);
#endif
}
}
/*
* Switches the key bindings to the given mode, if the mode exists
*
*/
void switch_mode(xcb_connection_t *conn, const char *new_mode) {
struct Mode *mode;
LOG("Switching to mode %s\n", new_mode);
SLIST_FOREACH(mode, &modes, modes) {
if (strcasecmp(mode->name, new_mode) != 0)
continue;
ungrab_all_keys(conn);
bindings = mode->bindings;
grab_all_keys(conn);
return;
}
LOG("ERROR: Mode not found\n");
}
/*
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
*
@ -143,13 +182,23 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
/* First ungrab the keys */
ungrab_all_keys(conn);
/* Clear the old binding and assignment lists */
struct Mode *mode;
Binding *bind;
while (!TAILQ_EMPTY(&bindings)) {
bind = TAILQ_FIRST(&bindings);
TAILQ_REMOVE(&bindings, bind, bindings);
FREE(bind->command);
FREE(bind);
while (!SLIST_EMPTY(&modes)) {
mode = SLIST_FIRST(&modes);
FREE(mode->name);
/* Clear the old binding list */
bindings = mode->bindings;
while (!TAILQ_EMPTY(bindings)) {
bind = TAILQ_FIRST(bindings);
TAILQ_REMOVE(bindings, bind, bindings);
FREE(bind->translated_to);
FREE(bind->command);
FREE(bind);
}
FREE(bindings);
SLIST_REMOVE(&modes, mode, Mode, modes);
}
struct Assignment *assign;
@ -161,6 +210,16 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
}
}
SLIST_INIT(&modes);
struct Mode *default_mode = scalloc(sizeof(struct Mode));
default_mode->name = sstrdup("default");
default_mode->bindings = scalloc(sizeof(struct bindings_head));
TAILQ_INIT(default_mode->bindings);
SLIST_INSERT_HEAD(&modes, default_mode, modes);
bindings = default_mode->bindings;
SLIST_HEAD(variables_head, Variable) variables;
#define OPTION_STRING(name) \
@ -202,14 +261,18 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
config.client.focused.background = get_colorpixel(conn, "#285577");
config.client.focused.text = get_colorpixel(conn, "#ffffff");
config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899");
config.client.focused_inactive.background = get_colorpixel(conn, "#555555");
config.client.focused_inactive.border = get_colorpixel(conn, "#333333");
config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a");
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
config.client.unfocused.border = get_colorpixel(conn, "#333333");
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
config.client.urgent.border = get_colorpixel(conn, "#2f343a");
config.client.urgent.background = get_colorpixel(conn, "#900000");
config.client.urgent.text = get_colorpixel(conn, "#ffffff");
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
config.bar.focused.background = get_colorpixel(conn, "#285577");
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
@ -218,6 +281,31 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
config.bar.urgent.border = get_colorpixel(conn, "#2f343a");
config.bar.urgent.background = get_colorpixel(conn, "#900000");
config.bar.urgent.text = get_colorpixel(conn, "#ffffff");
if (config_use_lexer) {
/* Yes, this will be cleaned up soon. */
if (override_configpath != NULL) {
parse_file(override_configpath);
} else {
FILE *handle;
char *globbed = glob_path("~/.i3/config");
if ((handle = fopen(globbed, "r")) == NULL) {
if ((handle = fopen("/etc/i3/config", "r")) == NULL) {
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
} else {
parse_file("/etc/i3/config");
}
} else {
parse_file(globbed);
}
}
if (reload)
grab_all_keys(conn);
} else {
FILE *handle;
if (override_configpath != NULL) {
if ((handle = fopen(override_configpath, "r")) == NULL)
@ -260,8 +348,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
OPTION_COLORTRIPLE("client.focused", client.focused);
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
OPTION_COLORTRIPLE("client.urgent", client.urgent);
OPTION_COLORTRIPLE("bar.focused", bar.focused);
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
OPTION_COLORTRIPLE("bar.urgent", bar.urgent);
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
@ -312,13 +402,22 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
rest++;
if (*rest != ' ')
die("Invalid binding (keysym)\n");
#if defined(__OpenBSD__)
size_t len = strlen(sym);
if (len > (rest - sym))
len = (rest - sym);
new->symbol = smalloc(len + 1);
memcpy(new->symbol, sym, len+1);
new->symbol[len]='\0';
#else
new->symbol = strndup(sym, (rest - sym));
#endif
}
rest++;
LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest);
new->mods = modifiers;
new->command = sstrdup(rest);
TAILQ_INSERT_TAIL(&bindings, new, bindings);
TAILQ_INSERT_TAIL(bindings, new, bindings);
continue;
}
@ -374,7 +473,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
if ((end = strchr(screen, ' ')) != NULL)
*end = '\0';
LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen);
workspaces[ws_num - 1].preferred_screen = screen;
workspace_get(ws_num-1)->preferred_screen = screen;
name += strlen("screen ") + strlen(screen);
}
@ -393,7 +492,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
LOG("setting name to \"%s\"\n", name);
if (*name != '\0')
workspace_set_name(&(workspaces[ws_num - 1]), name);
workspace_set_name(workspace_get(ws_num - 1), name);
free(ws_str);
continue;
}
@ -444,7 +543,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
while (*target == '~')
target++;
if (atoi(target) >= 1 && atoi(target) <= 10) {
if (atoi(target) >= 1) {
if (new->floating == ASSIGN_FLOATING_ONLY)
new->floating = ASSIGN_FLOATING;
new->workspace = atoi(target);
@ -493,9 +592,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
grab_all_keys(conn);
fclose(handle);
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
while (!SLIST_EMPTY(&variables)) {
struct Variable *v = SLIST_FIRST(&variables);
SLIST_REMOVE_HEAD(&variables, variables);
@ -503,10 +599,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
free(v->value);
free(v);
}
}
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font);
/* Set an empty name for every workspace which got no name */
for (int i = 0; i < 10; i++) {
Workspace *ws = &(workspaces[i]);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->name != NULL) {
/* If the font was not specified when the workspace name
* was loaded, we need to predict the text width now */
@ -516,7 +616,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
continue;
}
workspace_set_name(&(workspaces[i]), NULL);
workspace_set_name(ws, NULL);
}
return;

View File

@ -26,6 +26,7 @@
#include "layout.h"
#include "client.h"
#include "floating.h"
#include "workspace.h"
/*
* Toggles floating mode for the given client.
@ -43,17 +44,18 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
if (con == NULL) {
LOG("This client is already in floating (container == NULL), re-inserting\n");
Client *next_tiling;
SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients)
Workspace *ws = client->workspace;
SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
if (!client_is_floating(next_tiling))
break;
/* If there are no tiling clients on this workspace, there can only be one
* container: the first one */
if (next_tiling == TAILQ_END(&(client->workspace->focus_stack)))
con = client->workspace->table[0][0];
if (next_tiling == TAILQ_END(&(ws->focus_stack)))
con = ws->table[0][0];
else con = next_tiling->container;
/* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
LOG("destination container = %p\n", con);
Client *old_focused = con->currently_focused;
@ -152,7 +154,6 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (client->fullscreen)
client->workspace->fullscreen_client = client;
}
/*
@ -255,10 +256,37 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre
/* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
}
/*
* Called when the user right-clicked on the titlebar of a floating window to
* resize it.
* Calls the drag_pointer function with the resize_window callback
*
*/
void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_resize_window\n");
void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
int32_t new_width = old_rect->width + (new_x - event->root_x);
int32_t new_height = old_rect->height + (new_y - event->root_y);
/* Obey minimum window size */
if (new_width < 75 || new_height < 50)
return;
/* Reposition the client correctly while moving */
client->rect.width = new_width;
client->rect.height = new_height;
/* resize_client flushes */
resize_client(conn, client);
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback);
}
/*
* This function grabs your pointer and lets you drag stuff around (borders).
* Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received

View File

@ -116,7 +116,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
/* Find the binding */
Binding *bind;
TAILQ_FOREACH(bind, &bindings, bindings) {
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
@ -137,7 +137,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
/* No match? Then it was an actively grabbed key, that is with Mode_switch, and
the user did not press Mode_switch, so just pass it */
if (bind == TAILQ_END(&bindings)) {
if (bind == TAILQ_END(bindings)) {
xcb_allow_events(conn, ReplayKeyboard, event->time);
xcb_flush(conn);
return 1;
@ -170,7 +170,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
c_ws->current_row = current_row;
c_ws->current_col = current_col;
c_ws = &workspaces[screen->current_workspace];
c_ws = screen->current_workspace;
current_row = c_ws->current_row;
current_col = c_ws->current_col;
LOG("We're now on virtual screen number %d\n", screen->num);
@ -275,226 +275,6 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not
return 0;
}
/*
* Checks if the button press was on a stack window, handles focus setting and returns true
* if so, or false otherwise.
*
*/
static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) {
struct Stack_Window *stack_win;
SLIST_FOREACH(stack_win, &stack_wins, stack_windows) {
if (stack_win->window != event->event)
continue;
/* A stack window was clicked, we check if it was button4 or button5
which are scroll up / scroll down. */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN);
focus_window_in_container(conn, CUR_CELL, direction);
return true;
}
/* It was no scrolling, so we calculate the destination client by
dividing the Y position of the event through the height of a window
decoration and then set the focus to this client. */
i3Font *font = load_font(conn, config.font);
int decoration_height = (font->height + 2 + 2);
int destination = (event->event_y / decoration_height),
c = 0;
Client *client;
LOG("Click on stack_win for client %d\n", destination);
CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
if (c++ == destination) {
set_focus(conn, client, true);
return true;
}
return true;
}
return false;
}
/*
* Checks if the button press was on a bar, switches to the workspace and returns true
* if so, or false otherwise.
*
*/
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens) {
if (screen->bar != event->event)
continue;
LOG("Click on a bar\n");
/* Check if the button was one of button4 or button5 (scroll up / scroll down) */
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1);
for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add)
if (workspaces[i].screen == screen) {
workspace_show(conn, i+1);
return true;
}
return true;
}
int drawn = 0;
/* Because workspaces can be on different screens, we need to loop
through all of them and decide to count it based on its ->screen */
for (int i = 0; i < 10; i++) {
if (workspaces[i].screen != screen)
continue;
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
i, drawn, workspaces[i].text_width);
if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) {
workspace_show(conn, i+1);
return true;
}
drawn += workspaces[i].text_width + 5 + 5 + 2;
}
return true;
}
return false;
}
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
LOG("Button %d pressed\n", event->state);
/* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event);
bool border_click = false;
if (client == NULL) {
client = table_get(&by_parent, event->event);
border_click = true;
}
/* See if this was a click with the configured modifier. If so, we need
* to move around the client if it was floating. if not, we just process
* as usual. */
if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) {
if (client == NULL) {
LOG("Not handling, floating_modifier was pressed and no client found\n");
return 1;
}
if (client_is_floating(client)) {
floating_drag_window(conn, client, event);
return 1;
}
}
if (client == NULL) {
/* The client was neither on a clients titlebar nor on a client itself, maybe on a stack_window? */
if (button_press_stackwin(conn, event))
return 1;
/* Or on a bar? */
if (button_press_bar(conn, event))
return 1;
LOG("Could not handle this button press\n");
return 1;
}
/* Set focus in any case */
set_focus(conn, client, true);
/* Lets see if this was on the borders (= resize). If not, were done */
LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container;
int first, second;
if (client->dock) {
LOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
return 1;
}
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
/* Some clients (xfontsel for example) seem to pass clicks on their
* window to the parent window, thus we receive an event here which in
* reality is a border_click. Check for the position and fix state. */
if (border_click &&
event->event_x >= client->child_rect.x &&
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
LOG("Fixing border_click = false because of click in child\n");
border_click = false;
}
if (!border_click) {
LOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */
if (client_is_floating(client))
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
return 1;
}
/* Dont handle events inside the titlebar, only borders are interesting */
i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
LOG("click on titlebar\n");
/* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) {
/* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */
xcb_raise_window(conn, client->frame);
xcb_flush(conn);
floating_drag_window(conn, client, event);
}
return 1;
}
if (client_is_floating(client))
return floating_border_click(conn, client, event);
if (event->event_y < 2) {
/* This was a press on the top border */
if (con->row == 0)
return 1;
first = con->row - 1;
second = con->row;
orientation = O_HORIZONTAL;
} else if (event->event_y >= (client->rect.height - 2)) {
/* …bottom border */
first = con->row + (con->rowspan - 1);
if (!cell_exists(con->col, first) ||
(first == (con->workspace->rows-1)))
return 1;
second = first + 1;
orientation = O_HORIZONTAL;
} else if (event->event_x <= 2) {
/* …left border */
if (con->col == 0)
return 1;
first = con->col - 1;
second = con->col;
} else if (event->event_x > 2) {
/* …right border */
first = con->col + (con->colspan - 1);
LOG("column %d\n", first);
if (!cell_exists(first, con->row) ||
(first == (con->workspace->cols-1)))
return 1;
second = first + 1;
}
return resize_graphical_handler(conn, con->workspace, first, second, orientation, event);
}
/*
* A new window appeared on the screen (=was mapped), so lets manage it.
*
@ -581,6 +361,22 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
return 1;
}
/* Dock clients can be reconfigured in their height */
if (client->dock) {
LOG("Reconfiguring height of this dock client\n");
if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
LOG("Ignoring configure request, no height given\n");
return 1;
}
client->desired_height = event->height;
render_workspace(conn, c_ws->screen, c_ws);
xcb_flush(conn);
return 1;
}
if (client->fullscreen) {
LOG("Client is in fullscreen mode\n");
@ -706,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
/* If this workspace is currently active, we dont delete it */
i3Screen *screen;
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace == client->workspace->num) {
if (screen->current_workspace == client->workspace) {
workspace_active = true;
workspace_empty = false;
break;
@ -783,9 +579,11 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if (client->dock)
return 1;
if (client->container != NULL && client->container->mode == MODE_STACK)
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
return 1;
@ -848,9 +646,11 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
if (client->dock)
return 1;
if (client->container != NULL && client->container->mode == MODE_STACK)
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED))
render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0);
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
return 1;
@ -926,12 +726,16 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
if (client->dock)
return 1;
if (client->container == NULL || client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0);
if (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED))
decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
else {
uint32_t background_color;
if (client->urgent)
background_color = config.client.urgent.background;
/* Distinguish if the window is currently focused… */
if (CUR_CELL != NULL && CUR_CELL->currently_focused == client)
else if (CUR_CELL != NULL && CUR_CELL->currently_focused == client)
background_color = config.client.focused.background;
/* …or if it is the focused window in a not focused container */
else background_color = config.client.focused_inactive.background;
@ -949,8 +753,13 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
/* Draw a black background */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
if (client->titlebar_position == TITLEBAR_OFF) {
xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
xcb_rectangle_t crect = {2, 0, client->rect.width - (2 + 2), client->rect.height - 2};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
}
}
xcb_flush(conn);
return 1;
@ -1097,6 +906,46 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
return 1;
}
/*
* Handles the WM_HINTS property for extracting the urgency state of the window.
*
*/
int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window);
if (client == NULL) {
LOG("Received WM_HINTS for unknown client\n");
return 1;
}
xcb_wm_hints_t hints;
if (reply != NULL) {
if (!xcb_get_wm_hints_from_reply(&hints, reply))
return 1;
} else {
if (!xcb_get_wm_hints_reply(conn, xcb_get_wm_hints_unchecked(conn, client->child), &hints, NULL))
return 1;
}
/* Update the flag on the client directly */
client->urgent = (xcb_wm_hints_get_urgency(&hints) != 0);
CLIENT_LOG(client);
LOG("Urgency flag changed to %d\n", client->urgent);
workspace_update_urgent_flag(client->workspace);
redecorate_window(conn, client);
/* If the workspace this client is on is not visible, we need to redraw
* the workspace bar */
if (!workspace_is_visible(client->workspace)) {
i3Screen *screen = client->workspace->screen;
render_workspace(conn, screen, screen->current_workspace);
xcb_flush(conn);
}
return 1;
}
/*
* Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window.

View File

@ -77,10 +77,16 @@ static void ipc_handle_message(uint8_t *message, int size,
LOG("payload as a string = %s\n", message);
switch (message_type) {
case I3_IPC_MESSAGE_TYPE_COMMAND:
parse_command(global_conn, (const char*)message);
case I3_IPC_MESSAGE_TYPE_COMMAND: {
/* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */
char *command = scalloc(message_size);
strncpy(command, (const char*)message, message_size);
parse_command(global_conn, (const char*)command);
free(command);
break;
}
default:
LOG("unhandled ipc message\n");
break;
@ -148,20 +154,30 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
}
uint8_t *message = (uint8_t*)buf;
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
while (n > 0) {
LOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
/* The next 32 bit after the magic are the message size */
uint32_t message_size = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
/* The next 32 bit after the magic are the message size */
uint32_t message_size = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
/* The last 32 bits of the header are the message type */
uint32_t message_type = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
if (message_size > n) {
LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
return;
}
ipc_handle_message(message, n, message_size, message_type);
/* The last 32 bits of the header are the message type */
uint32_t message_type = *((uint32_t*)message);
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
ipc_handle_message(message, n, message_size, message_type);
n -= message_size;
message += message_size;
}
}
/*

View File

@ -27,6 +27,7 @@
#include "client.h"
#include "floating.h"
#include "handlers.h"
#include "workspace.h"
/*
* Updates *destination with new_value and returns true if it was changed or false
@ -63,16 +64,27 @@ int get_unoccupied_x(Workspace *workspace) {
}
/* See get_unoccupied_x() */
int get_unoccupied_y(Workspace *workspace, int col) {
int unoccupied = workspace->rect.height;
float default_factor = ((float)workspace->rect.height / workspace->rows) / workspace->rect.height;
int get_unoccupied_y(Workspace *workspace) {
int height = workspace->rect.height;
i3Font *font = load_font(global_conn, config.font);
/* Reserve space for dock clients */
Client *client;
SLIST_FOREACH(client, &(workspace->screen->dock_clients), dock_clients)
height -= client->desired_height;
/* Space for the internal bar */
height -= (font->height + 6);
int unoccupied = height;
float default_factor = ((float)height / workspace->rows) / height;
LOG("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
for (int rows = 0; rows < workspace->rows; rows++) {
LOG("height_factor[%d] = %f\n", rows, workspace->height_factor[rows]);
if (workspace->height_factor[rows] == 0)
unoccupied -= workspace->rect.height * default_factor;
unoccupied -= height * default_factor;
}
LOG("unoccupied space: %d\n", unoccupied);
@ -86,12 +98,14 @@ int get_unoccupied_y(Workspace *workspace, int col) {
*
*/
void redecorate_window(xcb_connection_t *conn, Client *client) {
if (client->container != NULL && client->container->mode == MODE_STACK) {
if (client->container != NULL &&
(client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED)) {
render_container(conn, client->container);
/* We clear the frame to generate exposure events, because the color used
in drawing may be different */
xcb_clear_area(conn, true, client->frame, 0, 0, client->rect.width, client->rect.height);
} else decorate_window(conn, client, client->frame, client->titlegc, 0);
} else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn);
}
@ -100,7 +114,8 @@ void redecorate_window(xcb_connection_t *conn, Client *client) {
* When in stacking mode, the window decorations are drawn onto an own window.
*
*/
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable,
xcb_gcontext_t gc, int offset_x, int offset_y) {
i3Font *font = load_font(conn, config.font);
int decoration_height = font->height + 2 + 2;
struct Colortriple *color;
@ -111,18 +126,23 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
return;
last_focused = SLIST_FIRST(&(client->workspace->focus_stack));
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (last_focused == client && c_ws == client->workspace)
/* Is the window urgent? */
if (client->urgent)
color = &(config.client.urgent);
else {
if (client_is_floating(client)) {
if (last_focused == client)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
else color = &(config.client.unfocused);
} else {
if (client->container->currently_focused == client) {
/* Distinguish if the window is currently focused… */
if (last_focused == client && c_ws == client->workspace)
color = &(config.client.focused);
/* …or if it is the focused window in a not focused container */
else color = &(config.client.focused_inactive);
} else color = &(config.client.unfocused);
}
}
/* Our plan is the following:
@ -132,16 +152,20 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
*/
/* Draw a rectangle in background color around the window */
if (client->borderless)
if (client->borderless && (client->container == NULL ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED)))
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */
if (client->container != NULL && client->container->mode == MODE_STACK) {
if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) {
/* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */
xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height };
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + client->container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
} else {
xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
@ -150,19 +174,28 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
/* Draw the inner background to have a black frame around clients (such as mplayer)
which cannot be resized exactly in our frames and therefore are centered */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
xcb_rectangle_t crect = {2, decoration_height,
client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
if (client->titlebar_position == TITLEBAR_OFF) {
xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else {
xcb_rectangle_t crect = {2, decoration_height,
client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height};
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
}
}
if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset);
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
if ((client->container == NULL ||
client->container->mode != MODE_STACK ||
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED) ||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3,
client->rect.width - 3, offset + font->height + 3);
xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */
offset_y + font->height + 3, /* y */
offset_x + client->rect.width - 3, /* to_x */
offset_y + font->height + 3 /* to_y */);
}
/* If the client has a title, we draw it */
@ -176,11 +209,11 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
and we dont handle the old window name (COMPOUND_TEXT) but only _NET_WM_NAME, which
is UTF-8 */
if (client->name_len == -1)
xcb_image_text_8(conn, strlen(client->name), drawable, gc, 3 /* X */,
offset + font->height /* Y = baseline of font */, client->name);
xcb_image_text_8(conn, strlen(client->name), drawable, gc, offset_x + 3 /* X */,
offset_y + font->height /* Y = baseline of font */, client->name);
else
xcb_image_text_16(conn, client->name_len, drawable, gc, 3 /* X */,
offset + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name);
xcb_image_text_16(conn, client->name_len, drawable, gc, offset_x + 3 /* X */,
offset_y + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name);
}
}
@ -210,7 +243,7 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
floating_assign_to_workspace(client, &workspaces[screen->current_workspace]);
floating_assign_to_workspace(client, screen->current_workspace);
}
/*
@ -242,6 +275,7 @@ void resize_client(xcb_connection_t *conn, Client *client) {
Rect *rect = &(client->child_rect);
switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
case MODE_STACK:
case MODE_TABBED:
rect->x = 2;
rect->y = 0;
rect->width = client->rect.width - (2 + 2);
@ -267,6 +301,9 @@ void resize_client(xcb_connection_t *conn, Client *client) {
break;
}
rect->width -= (2 * client->border_width);
rect->height -= (2 * client->border_width);
/* Obey the ratio, if any */
if (client->proportional_height != 0 &&
client->proportional_width != 0) {
@ -360,17 +397,38 @@ void render_container(xcb_connection_t *conn, Container *container) {
i3Font *font = load_font(conn, config.font);
int decoration_height = (font->height + 2 + 2);
struct Stack_Window *stack_win = &(container->stack_win);
/* The size for each tab (width), necessary as a separate variable
* because num_clients gets fixed to 1 in tabbed mode. */
int size_each = (num_clients == 0 ? container->width : container->width / num_clients);
int stack_lines = num_clients;
/* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */
if (stack_win->rect.height == 0 && num_clients > 0)
if (stack_win->rect.height == 0 && num_clients > 0) {
LOG("remapping stack win\n");
xcb_map_window(conn, stack_win->window);
} else LOG("not remapping stackwin, height = %d, num_clients = %d\n",
stack_win->rect.height, num_clients);
if (container->mode == MODE_TABBED) {
/* By setting num_clients to 1 we force that the stack window will be only one line
* high. The rest of the code is useful in both cases. */
LOG("tabbed mode, setting num_clients = 1\n");
if (stack_lines > 1)
stack_lines = 1;
}
if (container->stack_limit == STACK_LIMIT_COLS) {
stack_lines = ceil((float)num_clients / container->stack_limit_value);
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
stack_lines = min(num_clients, container->stack_limit_value);
}
/* Check if we need to reconfigure our stack title window */
if (update_if_necessary(&(stack_win->rect.x), container->x) |
update_if_necessary(&(stack_win->rect.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), decoration_height * num_clients)) {
update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) {
/* Configuration can happen in two slightly different ways:
@ -406,6 +464,22 @@ void render_container(xcb_connection_t *conn, Container *container) {
/* Prepare the pixmap for usage */
cached_pixmap_prepare(conn, &(stack_win->pixmap));
int current_row = 0, current_col = 0;
int wrap = 0;
if (container->stack_limit == STACK_LIMIT_COLS) {
/* wrap stores the number of rows after which we will
* wrap to a new column. */
wrap = ceil((float)num_clients / container->stack_limit_value);
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
/* When limiting rows, the wrap variable serves a
* slightly different purpose: it holds the number of
* pixels which each client will get. This is constant
* during the following loop, so it saves us some
* divisions and ceil()ing. */
wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
}
/* Render the decorations of all clients */
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */
@ -415,18 +489,67 @@ void render_container(xcb_connection_t *conn, Container *container) {
}
/* Check if we changed client->x or client->y by updating it.
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
* Note the bitwise OR instead of logical OR to force evaluation of all statements */
if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * num_clients)) |
update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) |
update_if_necessary(&(client->rect.width), container->width) |
update_if_necessary(&(client->rect.height), container->height - (decoration_height * num_clients)))
update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines)))
resize_client(conn, client);
client->force_reconfigure = false;
int offset_x = 0;
int offset_y = 0;
if (container->mode == MODE_STACK) {
if (container->stack_limit == STACK_LIMIT_COLS) {
offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
offset_y = current_row * decoration_height;
current_row++;
if ((current_row % wrap) == 0) {
current_col++;
current_row = 0;
}
} else if (container->stack_limit == STACK_LIMIT_ROWS) {
offset_x = current_col * wrap;
offset_y = current_row * decoration_height;
current_row++;
if ((current_row % container->stack_limit_value) == 0) {
current_col++;
current_row = 0;
}
} else {
offset_y = current_client * decoration_height;
}
current_client++;
} else if (container->mode == MODE_TABBED)
offset_x = current_client++ * size_each;
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc,
current_client++ * decoration_height);
offset_x, offset_y);
}
/* Check if we need to fill one column because of an uneven
* amount of windows */
if (container->mode == MODE_STACK) {
if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) {
xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value);
int offset_y = current_row * decoration_height;
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
} else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) {
xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
int offset_x = current_col * wrap;
int offset_y = current_row * decoration_height;
xcb_rectangle_t rect = {offset_x, offset_y,
offset_x + container->width,
offset_y + decoration_height };
xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect);
}
}
xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
@ -468,13 +591,18 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
int drawn = 0;
for (int c = 0; c < 10; c++) {
if (workspaces[c].screen != screen)
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen)
continue;
struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) :
&(config.bar.unfocused));
Workspace *ws = &workspaces[c];
struct Colortriple *color;
if (screen->current_workspace == ws)
color = &(config.bar.focused);
else if (ws->urgent)
color = &(config.bar.urgent);
else color = &(config.bar.unfocused);
/* Draw the outer rect */
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
@ -509,7 +637,10 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
Client *client;
uint32_t values[1];
FOR_TABLE(workspace)
FOR_TABLE(workspace) {
if (workspace->table[cols][rows] == NULL)
continue;
CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) {
/* Change event mask for the decorations */
values[0] = FRAME_EVENT_MASK;
@ -523,6 +654,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW);
xcb_change_window_attributes(conn, client->child, XCB_CW_EVENT_MASK, values);
}
}
}
/*
@ -555,7 +687,9 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
/* Go through the whole table and render whats necessary */
FOR_TABLE(r_ws) {
Container *container = r_ws->table[cols][rows];
int single_width = -1, single_height;
if (container == NULL)
continue;
int single_width = -1, single_height = -1;
/* Update position of the container */
container->row = rows;
container->col = cols;
@ -572,11 +706,18 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
single_width = container->width;
}
//if (container->height_factor == 0)
container->height = (height / r_ws->rows);
//else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
single_height = container->height;
container->height *= container->rowspan;
LOG("height is %d\n", height);
container->height = 0;
for (int c = 0; c < container->rowspan; c++) {
if (r_ws->height_factor[rows+c] == 0)
container->height += (height / r_ws->rows);
else container->height += get_unoccupied_y(r_ws) * r_ws->height_factor[rows+c];
if (single_height == -1)
single_height = container->height;
}
/* Render the container if it is not empty */
render_container(conn, container);
@ -602,8 +743,12 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
void render_layout(xcb_connection_t *conn) {
i3Screen *screen;
if (virtual_screens == NULL)
return;
TAILQ_FOREACH(screen, virtual_screens, screens)
render_workspace(conn, screen, &(workspaces[screen->current_workspace]));
if (screen->current_workspace != NULL)
render_workspace(conn, screen, screen->current_workspace);
xcb_flush(conn);
}

View File

@ -19,6 +19,7 @@
#include <limits.h>
#include <locale.h>
#include <fcntl.h>
#include <getopt.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
@ -38,6 +39,7 @@
#include "data.h"
#include "debug.h"
#include "handlers.h"
#include "click.h"
#include "i3.h"
#include "layout.h"
#include "queue.h"
@ -59,7 +61,7 @@ Display *xkbdpy;
xcb_key_symbols_t *keysyms;
/* The list of key bindings */
struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
struct bindings_head *bindings;
/* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
@ -147,6 +149,14 @@ int main(int argc, char *argv[], char *env[]) {
xcb_connection_t *conn;
xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int option_index = 0;
setlocale(LC_ALL, "");
@ -156,7 +166,7 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv;
while ((opt = getopt(argc, argv, "c:va")) != -1) {
while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
@ -168,8 +178,15 @@ int main(int argc, char *argv[], char *env[]) {
case 'v':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS);
case 'l':
config_use_lexer = true;
break;
default:
fprintf(stderr, "Usage: %s [-c configfile]\n", argv[0]);
fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "-a: disable autostart\n");
fprintf(stderr, "-v: display version and exit\n");
fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
exit(EXIT_FAILURE);
}
}
@ -189,6 +206,14 @@ int main(int argc, char *argv[], char *env[]) {
load_configuration(conn, override_configpath, false);
/* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X
* connection and a loaded configuration (default mode for new
* containers may be stacking, which requires a new window to be
* created), it had to be delayed. */
expand_table_cols(TAILQ_FIRST(workspaces));
expand_table_rows(TAILQ_FIRST(workspaces));
/* Place requests for the atoms we need as soon as possible */
#define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name);
@ -385,6 +410,9 @@ int main(int argc, char *argv[], char *env[]) {
/* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */
xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL);
/* Watch WM_HINTS (contains the urgent property) */
xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL);
/* Set up the atoms we support */
check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED],
ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
@ -422,12 +450,13 @@ int main(int argc, char *argv[], char *env[]) {
i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
if (screen == NULL) {
LOG("ERROR: No screen at %d x %d\n", reply->root_x, reply->root_y);
return 0;
LOG("ERROR: No screen at %d x %d, starting on the first screen\n",
reply->root_x, reply->root_y);
screen = TAILQ_FIRST(virtual_screens);
}
LOG("Starting on %d\n", screen->current_workspace);
c_ws = &workspaces[screen->current_workspace];
c_ws = screen->current_workspace;
manage_existing_windows(conn, &prophs, root);

View File

@ -50,11 +50,11 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
/* Request the window attributes for every window */
children = xcb_query_tree_children(reply);
for(i = 0; i < len; ++i)
for (i = 0; i < len; ++i)
cookies[i] = xcb_get_window_attributes(conn, children[i]);
/* Call manage_window with the attributes for every window */
for(i = 0; i < len; ++i)
for (i = 0; i < len; ++i)
manage_window(prophs, conn, children[i], cookies[i], true);
free(reply);
@ -99,12 +99,14 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
/* Reparent the window and add it to our list of managed windows */
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height);
geom->x, geom->y, geom->width, geom->height,
geom->border_width);
/* Generate callback events for every property we watch */
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
@ -124,7 +126,8 @@ out:
*/
void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height) {
int16_t x, int16_t y, uint16_t width, uint16_t height,
uint32_t border_width) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie,
@ -174,11 +177,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->rect.height = height;
new->width_increment = 1;
new->height_increment = 1;
new->border_width = border_width;
/* Pre-initialize the values for floating */
new->floating_rect.x = -1;
new->floating_rect.width = width;
new->floating_rect.height = height;
if (config.default_border != NULL)
client_init_border(conn, new, config.default_border[1]);
mask = 0;
/* Dont generate events for our new window, it should *not* be managed */
@ -231,7 +238,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
1 /* left mouse button */, XCB_MOD_MASK_1);
3 /* right mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
/* Get _NET_WM_WINDOW_TYPE (to see if its a dock) */
xcb_atom_t *atom;
@ -324,13 +332,13 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace == (assign->workspace-1)) {
if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n");
break;
}
LOG("Changing container/workspace and unmapping the client\n");
Workspace *t_ws = &(workspaces[assign->workspace-1]);
Workspace *t_ws = workspace_get(assign->workspace-1);
workspace_initialize(t_ws, c_ws->screen);
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];

View File

@ -54,13 +54,6 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
/* FIXME: horizontal resizing causes empty spaces to exist */
if (orientation == O_HORIZONTAL) {
LOG("Sorry, horizontal resizing is not yet activated due to creating layout bugs."
"If you are brave, enable the code for yourself and try fixing it.\n");
return 1;
}
uint32_t mask = 0;
uint32_t values[2];
@ -133,13 +126,26 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
xcb_destroy_window(conn, grabwin);
xcb_flush(conn);
if (orientation == O_VERTICAL) {
LOG("Resize was from X = %d to X = %d\n", event->root_x, new_position);
if (event->root_x == new_position) {
LOG("Nothing changed, not updating anything\n");
return 1;
}
int pixels;
if (orientation == O_VERTICAL)
pixels = (new_position - event->root_x);
else pixels = (new_position - event->root_y);
resize_container(conn, ws, first, second, orientation, pixels);
return 1;
}
/*
* Resizes a column/row by the given amount of pixels. Called by
* resize_graphical_handler (the user clicked) or parse_resize_command (the
* user issued the command)
*
*/
void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, int pixels) {
/* TODO: refactor this, both blocks are very identical */
if (orientation == O_VERTICAL) {
int default_width = ws->rect.width / ws->cols;
int old_unoccupied_x = get_unoccupied_x(ws);
@ -180,8 +186,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
LOG("middle = %f\n", ws->width_factor[first]);
int old_width = ws->width_factor[first] * old_unoccupied_x;
LOG("first->width = %d, new_position = %d, event->root_x = %d\n", old_width, new_position, event->root_x);
ws->width_factor[first] *= (float)(old_width + (new_position - event->root_x)) / old_width;
LOG("first->width = %d, pixels = %d\n", pixels);
ws->width_factor[first] *= (float)(old_width + pixels) / old_width;
LOG("-> %f\n", ws->width_factor[first]);
@ -190,33 +196,72 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[second]);
old_width = ws->width_factor[second] * old_unoccupied_x;
LOG("second->width = %d, new_position = %d, event->root_x = %d\n", old_width, new_position, event->root_x);
ws->width_factor[second] *= (float)(old_width - (new_position - event->root_x)) / old_width;
LOG("second->width = %d, pixels = %d\n", pixels);
ws->width_factor[second] *= (float)(old_width - pixels) / old_width;
LOG("-> %f\n", ws->width_factor[second]);
LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
LOG("\n\n\n");
} else {
#if 0
LOG("Resize was from Y = %d to Y = %d\n", event->root_y, new_position);
if (event->root_y == new_position) {
LOG("Nothing changed, not updating anything\n");
return 1;
}
int default_height = ws->rect.height / ws->rows;
int old_unoccupied_y = get_unoccupied_y(ws);
/* Convert 0 (for default height_factor) to actual numbers */
if (first->height_factor == 0)
first->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height;
if (second->height_factor == 0)
second->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height;
/* We pre-calculate the unoccupied space to see if we need to adapt sizes before
* doing the resize */
int new_unoccupied_y = old_unoccupied_y;
first->height_factor *= (float)(first->height + (new_position - event->root_y)) / first->height;
second->height_factor *= (float)(second->height - (new_position - event->root_y)) / second->height;
#endif
if (old_unoccupied_y == 0)
old_unoccupied_y = ws->rect.height;
if (ws->height_factor[first] == 0)
new_unoccupied_y += default_height;
if (ws->height_factor[second] == 0)
new_unoccupied_y += default_height;
LOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
/* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */
if (new_unoccupied_y != old_unoccupied_y)
for (int row = 0; row < ws->rows; row++) {
if (ws->height_factor[row] == 0)
continue;
LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y;
LOG("to %f\n", ws->height_factor[row]);
}
LOG("old_unoccupied_y = %d\n", old_unoccupied_y);
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0)
ws->height_factor[first] = ((float)ws->rect.height / ws->rows) / new_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[first]);
int old_height = ws->height_factor[first] * old_unoccupied_y;
LOG("first->width = %d, pixels = %d\n", pixels);
ws->height_factor[first] *= (float)(old_height + pixels) / old_height;
LOG("-> %f\n", ws->height_factor[first]);
LOG("Updating second (before = %f)\n", ws->height_factor[second]);
if (ws->height_factor[second] == 0)
ws->height_factor[second] = ((float)ws->rect.height / ws->rows) / new_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[second]);
old_height = ws->height_factor[second] * old_unoccupied_y;
LOG("second->width = %d, pixels = %d\n", pixels);
ws->height_factor[second] *= (float)(old_height - pixels) / old_height;
LOG("-> %f\n", ws->height_factor[second]);
LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
LOG("\n\n\n");
}
render_layout(conn);
return 1;
}

View File

@ -25,11 +25,14 @@
#include "util.h"
#include "i3.h"
#include "layout.h"
#include "config.h"
#include "workspace.h"
int current_workspace = 0;
Workspace workspaces[10];
int num_workspaces = 1;
struct workspaces_head *workspaces;
/* Convenience pointer to the current workspace */
Workspace *c_ws = &workspaces[0];
Workspace *c_ws;
int current_col = 0;
int current_row = 0;
@ -38,18 +41,16 @@ int current_row = 0;
*
*/
void init_table() {
memset(workspaces, 0, sizeof(workspaces));
workspaces = scalloc(sizeof(struct workspaces_head));
TAILQ_INIT(workspaces);
for (int i = 0; i < 10; i++) {
workspaces[i].screen = NULL;
workspaces[i].num = i;
TAILQ_INIT(&(workspaces[i].floating_clients));
expand_table_cols(&(workspaces[i]));
expand_table_rows(&(workspaces[i]));
}
c_ws = scalloc(sizeof(Workspace));
workspace_set_name(c_ws, NULL);
TAILQ_INIT(&(c_ws->floating_clients));
TAILQ_INSERT_TAIL(workspaces, c_ws, workspaces);
}
static void new_container(Workspace *workspace, Container **container, int col, int row) {
static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
Container *new;
new = *container = calloc(sizeof(Container), 1);
CIRCLEQ_INIT(&(new->clients));
@ -58,6 +59,10 @@ static void new_container(Workspace *workspace, Container **container, int col,
new->col = col;
new->row = row;
new->workspace = workspace;
if (!skip_layout_switch)
switch_layout_mode(global_conn, new, config.container_mode);
new->stack_limit = config.container_stack_limit;
new->stack_limit_value = config.container_stack_limit_value;
}
/*
@ -72,8 +77,14 @@ void expand_table_rows(Workspace *workspace) {
for (int c = 0; c < workspace->cols; c++) {
workspace->table[c] = realloc(workspace->table[c], sizeof(Container*) * workspace->rows);
new_container(workspace, &(workspace->table[c][workspace->rows-1]), c, workspace->rows-1);
new_container(workspace, &(workspace->table[c][workspace->rows-1]), c, workspace->rows-1, true);
}
/* We need to switch the layout in a separate step because it could
* happen that render_layout() (being called by switch_layout_mode())
* would access containers which were not yet initialized. */
for (int c = 0; c < workspace->cols; c++)
switch_layout_mode(global_conn, workspace->table[c][workspace->rows-1], config.container_mode);
}
/*
@ -105,7 +116,7 @@ void expand_table_rows_at_head(Workspace *workspace) {
}
for (int cols = 0; cols < workspace->cols; cols++)
new_container(workspace, &(workspace->table[cols][0]), cols, 0);
new_container(workspace, &(workspace->table[cols][0]), cols, 0, false);
}
/*
@ -120,8 +131,12 @@ void expand_table_cols(Workspace *workspace) {
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
for (int c = 0; c < workspace->rows; c++)
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c);
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true);
for (int c = 0; c < workspace->rows; c++)
switch_layout_mode(global_conn, workspace->table[workspace->cols-1][c], config.container_mode);
}
/*
@ -153,7 +168,7 @@ void expand_table_cols_at_head(Workspace *workspace) {
}
for (int rows = 0; rows < workspace->rows; rows++)
new_container(workspace, &(workspace->table[0][rows]), 0, rows);
new_container(workspace, &(workspace->table[0][rows]), 0, rows, false);
}
/*
@ -198,11 +213,29 @@ static void shrink_table_cols(Workspace *workspace) {
*
*/
static void shrink_table_rows(Workspace *workspace) {
float free_space = workspace->height_factor[workspace->rows-1];
workspace->rows--;
for (int cols = 0; cols < workspace->cols; cols++)
workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows);
}
/* Shrink the height_factor array */
workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
/* Distribute the free space */
if (free_space == 0)
return;
for (int rows = (workspace->rows-1); rows >= 0; rows--) {
if (workspace->height_factor[rows] == 0)
continue;
LOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
workspace->height_factor[rows]);
workspace->height_factor[rows] += free_space;
break;
}
}
/*
* Performs simple bounds checking for the given column/row
@ -216,7 +249,7 @@ bool cell_exists(int col, int row) {
static void free_container(xcb_connection_t *conn, Workspace *workspace, int col, int row) {
Container *old_container = workspace->table[col][row];
if (old_container->mode == MODE_STACK)
if (old_container->mode == MODE_STACK || old_container->mode == MODE_TABBED)
leave_stack_mode(conn, old_container);
free(old_container);

View File

@ -18,6 +18,9 @@
#include <stdarg.h>
#include <assert.h>
#include <iconv.h>
#if defined(__OpenBSD__)
#include <sys/cdefs.h>
#endif
#include <xcb/xcb_icccm.h>
@ -270,7 +273,8 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
Client *last_focused = get_last_focused_client(conn, client->container, NULL);
/* In stacking containers, raise the client in respect to the one which was focused before */
if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) {
if ((client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED) &&
client->container->workspace->fullscreen_client == NULL) {
/* We need to get the client again, this time excluding the current client, because
* we might have just gone into stacking mode and need to raise */
Client *last_focused = get_last_focused_client(conn, client->container, client);
@ -339,15 +343,22 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container) {
*
*/
void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) {
if (mode == MODE_STACK) {
if (mode == MODE_STACK || mode == MODE_TABBED) {
/* When were already in stacking mode, nothing has to be done */
if (container->mode == MODE_STACK)
if ((mode == MODE_STACK && container->mode == MODE_STACK) ||
(mode == MODE_TABBED && container->mode == MODE_TABBED))
return;
/* When entering stacking mode, we need to open a window on which we can draw the
title bars of the clients, it has height 1 because we dont bother here with
calculating the correct height - it will be adjusted when rendering anyways. */
Rect rect = {container->x, container->y, container->width, 1};
if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
goto after_stackwin;
/* When entering stacking mode, we need to open a window on
* which we can draw the title bars of the clients, it has
* height 1 because we dont bother here with calculating the
* correct height - it will be adjusted when rendering anyways.
* Also, we need to use max(width, 1) because windows cannot
* be created with either width == 0 or height == 0. */
Rect rect = {container->x, container->y, max(container->width, 1), 1};
uint32_t mask = 0;
uint32_t values[2];
@ -377,9 +388,10 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows);
} else {
if (container->mode == MODE_STACK)
if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
leave_stack_mode(conn, container);
}
after_stackwin:
container->mode = mode;
/* Force reconfiguration of each client */
@ -446,12 +458,13 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl
}
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
for (int workspace = 0; workspace < 10; workspace++) {
if (workspaces[workspace].screen == NULL)
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen == NULL)
continue;
Client *client;
SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) {
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
@ -466,3 +479,40 @@ done:
FREE(to_title_ucs);
return matching;
}
#if defined(__OpenBSD__)
/*
* Taken from FreeBSD
* Find the first occurrence of the byte string s in byte string l.
*
*/
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
register char *cur, *last;
const char *cl = (const char *)l;
const char *cs = (const char *)s;
/* we need something to compare */
if (l_len == 0 || s_len == 0)
return NULL;
/* "s" must be smaller or equal to "l" */
if (l_len < s_len)
return NULL;
/* special case where s_len == 1 */
if (s_len == 1)
return memchr(l, (int)*cs, l_len);
/* the last position where its possible to find "s" in "l" */
last = (char *)cl + l_len - s_len;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
return cur;
return NULL;
}
#endif

View File

@ -27,6 +27,40 @@
#include "workspace.h"
#include "client.h"
/*
* Returns a pointer to the workspace with the given number (starting at 0),
* creating the workspace if necessary (by allocating the necessary amount of
* memory and initializing the data structures correctly).
*
*/
Workspace *workspace_get(int number) {
Workspace *ws = NULL;
TAILQ_FOREACH(ws, workspaces, workspaces)
if (ws->num == number)
return ws;
/* If we are still there, we could not find the requested workspace. */
int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
LOG("We need to initialize that one, last ws = %d\n", last_ws);
for (int c = last_ws; c < number; c++) {
LOG("Creating new ws\n");
ws = scalloc(sizeof(Workspace));
ws->num = c+1;
TAILQ_INIT(&(ws->floating_clients));
expand_table_cols(ws);
expand_table_rows(ws);
workspace_set_name(ws, NULL);
TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
}
LOG("done\n");
return ws;
}
/*
* Sets the name (or just its number) for the given workspace. This has to
* be called for every workspace as the rendering function
@ -62,7 +96,7 @@ void workspace_set_name(Workspace *ws, const char *name) {
*
*/
bool workspace_is_visible(Workspace *ws) {
return (ws->screen->current_workspace == ws->num);
return (ws->screen->current_workspace == ws);
}
/*
@ -73,7 +107,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
bool need_warp = false;
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
/* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = &(workspaces[workspace-1]);
Workspace *t_ws = workspace_get(workspace-1);
LOG("show_workspace(%d)\n", workspace);
@ -91,7 +125,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
/* Store the old client */
Client *old_client = CUR_CELL->currently_focused;
c_ws = &(workspaces[t_ws->screen->current_workspace]);
c_ws = t_ws->screen->current_workspace;
current_col = c_ws->current_col;
current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL)
@ -109,7 +143,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
}
/* Check if we need to change something or if were already there */
if (c_ws->screen->current_workspace == (workspace-1)) {
if (c_ws->screen->current_workspace->num == (workspace-1)) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack)))
set_focus(conn, last_focused, true);
@ -121,9 +155,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
return;
}
t_ws->screen->current_workspace = workspace-1;
Workspace *old_workspace = c_ws;
c_ws = &workspaces[workspace-1];
c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1);
/* Unmap all clients of the old workspace */
workspace_unmap_clients(conn, old_workspace);
@ -229,7 +262,6 @@ void workspace_initialize(Workspace *ws, i3Screen *screen) {
if (ws->preferred_screen == NULL ||
(ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
ws->screen = screen;
else { LOG("yay, found assignment\n"); }
/* Copy the dimensions from the virtual screen */
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
@ -243,8 +275,8 @@ void workspace_initialize(Workspace *ws, i3Screen *screen) {
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
Workspace *result = NULL;
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->preferred_screen == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
continue;
@ -255,22 +287,28 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
if (result == NULL) {
/* No assignment found, returning first unused workspace */
for (int c = 0; c < 10; c++) {
if (workspaces[c].screen != NULL)
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != NULL)
continue;
result = &(workspaces[c]);
result = ws;
break;
}
}
if (result != NULL) {
workspace_initialize(result, screen);
return result;
if (result == NULL) {
LOG("No existing free workspace found to assign, creating a new one\n");
Workspace *ws;
int last_ws = 0;
TAILQ_FOREACH(ws, workspaces, workspaces)
last_ws = ws->num;
result = workspace_get(last_ws + 1);
}
LOG("WARNING: No free workspace found to assign!\n");
return NULL;
workspace_initialize(result, screen);
return result;
}
/*
@ -361,3 +399,21 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
ignore_enter_notify_forall(conn, u_ws, false);
}
/*
* Goes through all clients on the given workspace and updates the workspaces
* urgent flag accordingly.
*
*/
void workspace_update_urgent_flag(Workspace *ws) {
Client *current;
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (!current->urgent)
continue;
ws->urgent = true;
return;
}
ws->urgent = false;
}

View File

@ -133,7 +133,7 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac
i3Font *font = load_font(conn, config.font);
workspace->screen = screen;
screen->current_workspace = workspace->num;
screen->current_workspace = workspace;
/* Create a bar for each screen */
Rect bar_rect = {screen->rect.x,
@ -298,12 +298,13 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
int screen_count = 0;
/* Mark each workspace which currently is assigned to a screen, so we
* can garbage-collect afterwards */
for (int c = 0; c < 10; c++)
workspaces[c].reassigned = (workspaces[c].screen == NULL);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
ws->reassigned = (ws->screen == NULL);
TAILQ_FOREACH(screen, new_screens, screens) {
screen->num = screen_count;
screen->current_workspace = -1;
screen->current_workspace = NULL;
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (old_screen->num != screen_count)
@ -332,10 +333,11 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
/* Copy the list head for the dock clients */
screen->dock_clients = old_screen->dock_clients;
SLIST_INIT(&(old_screen->dock_clients));
/* Update the dimensions */
for (int c = 0; c < 10; c++) {
Workspace *ws = &(workspaces[c]);
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != old_screen)
continue;
@ -347,13 +349,14 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
break;
}
if (screen->current_workspace == -1) {
if (screen->current_workspace == NULL) {
/* Find the first unused workspace, preferring the ones
* which are assigned to this screen and initialize
* the screen with it. */
LOG("getting first ws for screen %p\n", screen);
Workspace *ws = get_first_workspace_for_screen(new_screens, screen);
initialize_screen(conn, screen, ws);
ws->reassigned = true;
/* As this workspace just got visible (we got a new screen
* without workspace), we need to map its clients */
@ -362,39 +365,58 @@ void xinerama_requery_screens(xcb_connection_t *conn) {
screen_count++;
}
/* Check for workspaces which are out of bounds */
for (int c = 0; c < 10; c++) {
if (workspaces[c].reassigned)
/* check for dock_clients which are out of bounds */
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
if (SLIST_EMPTY(&(old_screen->dock_clients)))
continue;
LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen);
if (SLIST_EMPTY(&(first->dock_clients))) {
first->dock_clients = old_screen->dock_clients;
continue;
}
/* We need to merge the lists */
Client *dock_client;
while (!SLIST_EMPTY(&(old_screen->dock_clients))) {
dock_client = SLIST_FIRST(&(old_screen->dock_clients));
SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients);
SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients);
}
}
/* Check for workspaces which are out of bounds */
TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->reassigned)
continue;
/* f_ws is a shortcut to the workspace to fix */
Workspace *f_ws = &(workspaces[c]);
Client *client;
LOG("Closing bar window (%p)\n", f_ws->screen->bar);
xcb_destroy_window(conn, f_ws->screen->bar);
LOG("Closing bar window (%p)\n", ws->screen->bar);
xcb_destroy_window(conn, ws->screen->bar);
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1);
f_ws->screen = first;
memcpy(&(f_ws->rect), &(first->rect), sizeof(Rect));
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1);
ws->screen = first;
memcpy(&(ws->rect), &(first->rect), sizeof(Rect));
/* Force reconfiguration for each client on that workspace */
FOR_TABLE(f_ws)
CIRCLEQ_FOREACH(client, &(f_ws->table[cols][rows]->clients), clients)
FOR_TABLE(ws)
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
client->force_reconfigure = true;
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
render_workspace(conn, first, f_ws);
render_workspace(conn, first, ws);
/* …unless we want to see them at the moment, we should hide that workspace */
if (workspace_is_visible(f_ws))
if (workspace_is_visible(ws))
continue;
workspace_unmap_clients(conn, f_ws);
workspace_unmap_clients(conn, ws);
if (c_ws == f_ws) {
if (c_ws == ws) {
LOG("Need to adjust c_ws...\n");
c_ws = &(workspaces[first->current_workspace]);
c_ws = first->current_workspace;
}
}
xcb_flush(conn);

4
testcases/Makefile Normal file
View File

@ -0,0 +1,4 @@
test:
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/*.t
all: test

10
testcases/t/00-load.t Normal file
View File

@ -0,0 +1,10 @@
#!perl
use Test::More tests => 2;
BEGIN {
use_ok( 'X11::XCB::Connection' );
use_ok( 'X11::XCB::Window' );
}
diag( "Testing i3, Perl $], $^X" );

34
testcases/t/01-tile.t Normal file
View File

@ -0,0 +1,34 @@
#!perl
use Test::More tests => 4;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
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',
);
isa_ok($window, 'X11::XCB::Window');
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->map;
sleep(0.25);
my $new_rect = $window->rect;
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
diag( "Testing i3, Perl $], $^X" );

View File

@ -0,0 +1,65 @@
#!perl
use Test::More tests => 8;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
# We use relatively long sleeps (1/4 second) to make sure the window manager
# reacted.
use Time::HiRes qw(sleep);
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',
);
isa_ok($window, 'X11::XCB::Window');
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->map;
sleep(0.25);
my $new_rect = $window->rect;
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned");
$original_rect = $new_rect;
sleep(0.25);
$window->fullscreen(1);
sleep(0.25);
$new_rect = $window->rect;
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
$window->unmap;
$window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => $original_rect,
background_color => 61440,
);
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->fullscreen(1);
$window->map;
sleep(0.25);
ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen");
ok($window->mapped, "Window is mapped after opening it in fullscreen mode");
diag( "Testing i3, Perl $], $^X" );

View File

@ -0,0 +1,35 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 5;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
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,
override_redirect => 1,
background_color => '#C0C0C0',
);
isa_ok($window, 'X11::XCB::Window');
is_deeply($window->rect, $original_rect, "rect unmodified before mapping");
$window->map;
my $new_rect = $window->rect;
isa_ok($new_rect, 'X11::XCB::Rect');
is_deeply($new_rect, $original_rect, "window untouched");
diag( "Testing i3, Perl $], $^X" );

66
testcases/t/04-floating.t Normal file
View File

@ -0,0 +1,66 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 10;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
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
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
sleep(0.25);
my ($absolute, $top) = $window->rect;
ok($window->mapped, 'Window is mapped');
ok($absolute->{width} >= 75, 'i3 raised the width to 75');
ok($absolute->{height} >= 50, 'i3 raised the height to 50');
ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
$window->unmap;
$window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 1, 1, 80, 90],
background_color => '#C0C0C0',
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
sleep(0.25);
($absolute, $top) = $window->rect;
ok($absolute->{width} == 80, "i3 let the width at 80");
ok($absolute->{height} == 90, "i3 let the height at 90");
ok($top->{x} == 1 && $top->{y} == 1, "i3 mapped it to (1,1)");
$window->unmap;
diag( "Testing i3, Perl $], $^X" );

51
testcases/t/05-ipc.t Normal file
View File

@ -0,0 +1,51 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 4;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
#####################################################################
# Ensure IPC works by switching workspaces
#####################################################################
# Switch to the first workspace to get a clean testing environment
$sock->write(i3test::format_ipc_command("1"));
sleep(0.25);
# Create a window so we can get a focus different from NULL
my $window = i3test::open_standard_window($x);
diag("window->id = " . $window->id);
sleep(0.25);
my $focus = $x->input_focus;
diag("old focus = $focus");
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
my $new_focus = $x->input_focus;
isnt($focus, $new_focus, "Focus changed");
diag( "Testing i3, Perl $], $^X" );

79
testcases/t/06-focus.t Normal file
View File

@ -0,0 +1,79 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 8;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
#####################################################################
# Create two windows and make sure focus switching works
#####################################################################
# Change mode of the container to "default" for following tests
$sock->write(i3test::format_ipc_command("d"));
sleep(0.25);
my $top = i3test::open_standard_window($x);
my $mid = i3test::open_standard_window($x);
my $bottom = i3test::open_standard_window($x);
sleep(0.25);
diag("top id = " . $top->id);
diag("mid id = " . $mid->id);
diag("bottom id = " . $bottom->id);
#
# Returns the input focus after sending the given command to i3 via IPC
# end sleeping for half a second to make sure i3 reacted
#
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
return $x->input_focus;
}
$focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("k");
is($focus, $mid->id, "Middle window focused");
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");
#####################################################################
# Test focus wrapping
#####################################################################
$focus = focus_after("k");
is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
$focus = focus_after("j");
is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
diag( "Testing i3, Perl $], $^X" );

90
testcases/t/07-move.t Normal file
View File

@ -0,0 +1,90 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 10;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
#####################################################################
# Create two windows and make sure focus switching works
#####################################################################
my $top = i3test::open_standard_window($x);
sleep(0.25);
my $mid = i3test::open_standard_window($x);
sleep(0.25);
my $bottom = i3test::open_standard_window($x);
sleep(0.25);
diag("top id = " . $top->id);
diag("mid id = " . $mid->id);
diag("bottom id = " . $bottom->id);
#
# Returns the input focus after sending the given command to i3 via IPC
# end sleeping for half a second to make sure i3 reacted
#
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
return $x->input_focus;
}
$focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("ml");
is($focus, $bottom->id, "Right window still focused");
$focus = focus_after("h");
is($focus, $mid->id, "Middle window focused");
#####################################################################
# Now move to the top window, move right, then move left again
# (e.g., does i3 remember the focus in the last container?)
#####################################################################
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");
$focus = focus_after("l");
is($focus, $bottom->id, "Right window focused");
$focus = focus_after("h");
is($focus, $top->id, "Top window focused");
#####################################################################
# Move window cross-workspace
#####################################################################
$sock->write(i3test::format_ipc_command("m12"));
$sock->write(i3test::format_ipc_command("t"));
$sock->write(i3test::format_ipc_command("m13"));
$sock->write(i3test::format_ipc_command("12"));
$sock->write(i3test::format_ipc_command("13"));
ok(1, "Still living");

View File

@ -0,0 +1,61 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Checks if the focus is correctly restored, when creating a floating client
# over an unfocused tiling client and destroying the floating one again.
use Test::More tests => 6;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
my $tiled_left = i3test::open_standard_window($x);
my $tiled_right = i3test::open_standard_window($x);
sleep(0.25);
$sock->write(i3test::format_ipc_command("ml"));
# 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 = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 1, 1, 30, 30],
background_color => '#C0C0C0',
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
isa_ok($window, 'X11::XCB::Window');
$window->map;
sleep(0.25);
is($x->input_focus, $window->id, 'floating window focused');
$window->unmap;
sleep(0.25);
is($x->input_focus, $focus, 'Focus correctly restored');
diag( "Testing i3, Perl $], $^X" );

138
testcases/t/09-stacking.t Normal file
View File

@ -0,0 +1,138 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 24;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
#####################################################################
# Create two windows and make sure focus switching works
#####################################################################
my $top = i3test::open_standard_window($x);
sleep(0.25);
my $mid = i3test::open_standard_window($x);
sleep(0.25);
my $bottom = i3test::open_standard_window($x);
sleep(0.25);
diag("top id = " . $top->id);
diag("mid id = " . $mid->id);
diag("bottom id = " . $bottom->id);
#
# Returns the input focus after sending the given command to i3 via IPC
# end sleeping for half a second to make sure i3 reacted
#
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.25);
return $x->input_focus;
}
$focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("s");
is($focus, $bottom->id, "Last window still focused");
$focus = focus_after("k");
is($focus, $mid->id, "Middle window focused");
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");
#####################################################################
# Test focus wrapping
#####################################################################
$focus = focus_after("k");
is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
$focus = focus_after("j");
is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
#####################################################################
# Restore of focus after moving windows out/into the stack
#####################################################################
$focus = focus_after("ml");
is($focus, $top->id, "Top window still focused (focus after moving)");
$focus = focus_after("h");
is($focus, $bottom->id, "Bottom window focused (focus after moving)");
my $new = i3test::open_standard_window($x);
sleep(0.25);
# By now, we have this layout:
# ----------------
# | mid |
# | bottom | top
# | new |
# ----------------
$focus = focus_after("l");
is($focus, $top->id, "Got top window");
$focus = focus_after("mh");
is($focus, $top->id, "Moved it into the stack");
$focus = focus_after("k");
is($focus, $new->id, "Window above is new");
$focus = focus_after("k");
is($focus, $bottom->id, "Window above is bottom");
$focus = focus_after("k");
is($focus, $mid->id, "Window above is mid");
$focus = focus_after("k");
is($focus, $top->id, "At top again");
$focus = focus_after("ml");
is($focus, $top->id, "Still at top, moved out");
$focus = focus_after("h");
is($focus, $mid->id, "At mid again");
$focus = focus_after("j");
is($focus, $bottom->id, "At bottom again");
$focus = focus_after("l");
is($focus, $top->id, "At top again");
$focus = focus_after("mh");
is($focus, $top->id, "Still at top, moved into");
$focus = focus_after("k");
is($focus, $bottom->id, "Window above is bottom");
$focus = focus_after("k");
is($focus, $mid->id, "Window above is mid");

46
testcases/t/10-dock.t Normal file
View File

@ -0,0 +1,46 @@
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 2;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use lib "$FindBin::Bin/lib";
use i3test;
use List::Util qw(first);
BEGIN {
#use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
#####################################################################
# Create a dock window and see if it gets managed
#####################################################################
my $screens = $x->screens;
# Get the primary screen
my $primary = first { $_->primary } @{$screens};
# TODO: focus the primary screen before
my $window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30],
background_color => '#FF0000',
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
);
$window->map;
sleep 0.25;
my $rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
diag( "Testing i3, Perl $], $^X" );

84
testcases/t/11-goto.t Normal file
View File

@ -0,0 +1,84 @@
#!perl
# vim:ts=4:sw=4:expandtab
# Beware that this test uses workspace 9 to perform some tests (it expects
# the workspace to be empty).
# TODO: skip it by default?
use Test::More tests => 9;
use Test::Deep;
use X11::XCB qw(:all);
use Data::Dumper;
use Time::HiRes qw(sleep);
use FindBin;
use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib";
use i3test;
BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
}
my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9"));
sleep(0.25);
#####################################################################
# Create two windows and make sure focus switching works
#####################################################################
my $top = i3test::open_standard_window($x);
sleep(0.25);
my $mid = i3test::open_standard_window($x);
sleep(0.25);
my $bottom = i3test::open_standard_window($x);
sleep(0.25);
diag("top id = " . $top->id);
diag("mid id = " . $mid->id);
diag("bottom id = " . $bottom->id);
#
# Returns the input focus after sending the given command to i3 via IPC
# end sleeping for half a second to make sure i3 reacted
#
sub focus_after {
my $msg = shift;
$sock->write(i3test::format_ipc_command($msg));
sleep(0.5);
return $x->input_focus;
}
$focus = $x->input_focus;
is($focus, $bottom->id, "Latest window focused");
$focus = focus_after("ml");
is($focus, $bottom->id, "Right window still focused");
$focus = focus_after("h");
is($focus, $mid->id, "Middle window focused");
#####################################################################
# Now goto a mark which does not exist
#####################################################################
my $random_mark = sha1_base64(rand());
$focus = focus_after("goto $random_mark");
is($focus, $mid->id, "focus unchanged");
$sock->write(i3test::format_ipc_command("mark $random_mark"));
$focus = focus_after("k");
is($focus, $top->id, "Top window focused");
$focus = focus_after("goto $random_mark");
is($focus, $mid->id, "goto worked");

43
testcases/t/lib/i3test.pm Normal file
View File

@ -0,0 +1,43 @@
package i3test;
# vim:ts=4:sw=4:expandtab
use X11::XCB::Rect;
use X11::XCB::Window;
use X11::XCB qw(:all);
BEGIN {
my $window_count = 0;
sub counter_window {
return $window_count++;
}
}
sub open_standard_window {
my ($x) = @_;
my $window = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#C0C0C0',
);
$window->name('Window ' . counter_window());
$window->map;
sleep(0.25);
return $window;
}
sub format_ipc_command {
my $msg = shift;
my $len;
{ use bytes; $len = length($msg); }
my $message = "i3-ipc" . pack("LL", $len, 0) . $msg;
return $message;
}
1