pax_global_header 0000666 0000000 0000000 00000000064 14470210147 0014512 g ustar 00root root 0000000 0000000 52 comment=ff5cc8863672d91a8ea41be0d898ccf57c4352fb
xcffib-1.5.0/ 0000775 0000000 0000000 00000000000 14470210147 0012756 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/.github/ 0000775 0000000 0000000 00000000000 14470210147 0014316 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14470210147 0016353 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/.github/workflows/ci.yaml 0000664 0000000 0000000 00000002161 14470210147 0017632 0 ustar 00root root 0000000 0000000 name: ci
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0' # weekly
jobs:
build:
runs-on: ubuntu-latest
name: "python ${{ matrix.python-version }} xcbver ${{ matrix.xcbver }}"
strategy:
fail-fast: false
matrix:
python-version: [3.8, 3.9, "3.10", "3.11", "pypy3.9"]
xcbver: [xcb-proto-1.14.1, xcb-proto-1.15.2, xcb-proto-1.16.0, master]
steps:
- uses: actions/checkout@v3
- name: Set up python "${{ matrix.python-version }}"
uses: actions/setup-python@v4
with:
python-version: "${{ matrix.python-version }}"
- uses: haskell/actions/setup@v2
with:
ghc-version: '9.2'
cabal-version: latest
- run: cabal update
- run: sudo apt install x11-apps python3-pytest python3-cffi flake8
- run: git clone https://gitlab.freedesktop.org/xorg/proto/xcbproto.git proto && cd proto && git checkout ${{ matrix.xcbver }}
- run: make XCBDIR=./proto/src check
xcffib-1.5.0/.github/workflows/release.yaml 0000664 0000000 0000000 00000000463 14470210147 0020662 0 ustar 00root root 0000000 0000000 name: "tagged-release"
on:
push:
tags:
- "v*"
jobs:
tagged-release:
name: "Tagged Release"
runs-on: "ubuntu-latest"
steps:
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
xcffib-1.5.0/.gitignore 0000664 0000000 0000000 00000000421 14470210147 0014743 0 ustar 00root root 0000000 0000000 build
dist
dist-newstyle
*.swp
*.swo
*.pyc
*__pycache__*
*.egg-info
xcffib
build/
*egg*
*deb
debian/*.log
debian/files
debian/tmp
debian/python-xcffib*
debian/python3-xcffib*
.pybuild
.cabal-sandbox/
.mypy_cache/
cabal.sandbox.config
cabal.project.local*
.ghc.environment*
xcffib-1.5.0/LICENSE 0000664 0000000 0000000 00000023676 14470210147 0014001 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
xcffib-1.5.0/MANIFEST.in 0000664 0000000 0000000 00000000153 14470210147 0014513 0 ustar 00root root 0000000 0000000 include README.md
include LICENSE
exclude xcffib/_ffi.py
include test/__init__.py
include test/conftest.py
xcffib-1.5.0/Makefile 0000664 0000000 0000000 00000006047 14470210147 0014425 0 ustar 00root root 0000000 0000000 AUTOPEP8=autopep8 --in-place --aggressive --aggressive
XCBDIR?=$(shell pkg-config --variable=xcbincludedir xcb-proto)
ifneq ($(XCBDIR),$(shell pkg-config --variable=xcbincludedir xcb-proto))
XCBVER=$(shell sed -e '1,/AC_INIT/d' $(XCBDIR)/../configure.ac | head -n 1 | tr -d ,[:blank:])
else
XCBVER=$(shell pkg-config --modversion xcb-proto)
endif
NCPUS=$(shell grep -c processor /proc/cpuinfo)
PARALLEL=$(shell which parallel)
CABAL=cabal --config-file=./cabal.config
GEN=$(CABAL) new-run --minimize-conflict-set -j$(NCPUS) exe:xcffibgen --
# you should have xcb-proto installed to run this
xcffib: module/*.py xcffib.cabal $(shell find . -path ./test -prune -false -o -name \*.hs)
$(GEN) --input $(XCBDIR) --output ./xcffib
cp ./module/*py ./xcffib/
touch ./xcffib/py.typed
sed -i "s/__xcb_proto_version__ = .*/__xcb_proto_version__ = \"${XCBVER}\"/" xcffib/__init__.py
.PHONY: xcffib-fmt
xcffib-fmt: module/*.py
ifeq (${PARALLEL},)
$(AUTOPEP8) ./xcffib/*.py
else
find ./xcffib/*.py | parallel -j $(NCPUS) $(AUTOPEP8) '{}'
endif
dist-newstyle:
$(CABAL) new-configure --enable-tests
.PHONY: gen
gen: dist-newstyle
$(CABAL) new-build -j$(NCPUS)
.PHONY: clean
clean:
-$(CABAL) new-clean
-rm -rf xcffib
-rm -rf module/*pyc module/__pycache__
-rm -rf test/*pyc test/__pycache__
-rm -rf build *egg* *deb .pybuild dist
-rm -rf .pc cabal.project.local*
valgrind: xcffib
valgrind --leak-check=full --show-leak-kinds=definite pytest-3 -v
newtests:
$(GEN) --input ./test/generator/ --output ./test/generator/
git diff test
# These are all split out so make -j3 check goes as fast as possible.
.PHONY: lint
lint:
flake8 --config=./test/flake8.cfg ./module
.PHONY: htests
htests:
$(CABAL) new-test -j$(NCPUS) --enable-tests
# The --builtin=CW is a hack to work around:
# https://lists.freedesktop.org/archives/xcb/2022-December/011427.html
# when that lands and all tested versions have it, we can
# drop this.
#
# In the meantime, we can work around this in the binding if someone really needs it.
ifeq ($(shell printf "$(XCBVER)\n1.16.0" | sort -V | grep -c 1.16.0), 1)
CW_HACK=--builtins=CW
endif
check: xcffib lint htests
flake8 -j$(NCPUS) --ignore=E128,E231,E251,E301,E302,E305,E501,F401,E402,W503,E741,E999 xcffib/*.py $(CW_HACK)
python3 -m compileall xcffib
pytest-3 -v --durations=3
# make release ver=0.99.99
release: xcffib
ifeq (${ver},)
@echo "no version (ver=) specified, not releasing."
else ifneq ($(wildcard ./xcffib.egg-info*),)
@echo "xcffib.egg-info exists, not releasing."
else
sed -i "s/version = .*/version = \"${ver}\"/" setup.py
sed -i "s/__version__ = .*/__version__ = \"${ver}\"/" xcffib/__init__.py
sed -r -i -e "s/(^version = \s*)[\"0-9\.]*/\1\"${ver}\"/" setup.py
sed -r -i -e "s/(^version:\s*)[0-9\.]*/\1${ver}/" xcffib.cabal
echo "Release v${ver}" > /tmp/xcffib.releasemsg
git commit -a -S -s --allow-empty-message -t /tmp/xcffib.releasemsg
git tag v${ver}
python3 setup.py sdist
twine upload dist/xcffib-${ver}.tar.gz
cabal new-sdist
cabal upload --publish dist-newstyle/sdist/xcffib-${ver}.tar.gz
@echo "remember to push the tag!!!"
endif
xcffib-1.5.0/README.md 0000664 0000000 0000000 00000013005 14470210147 0014234 0 ustar 00root root 0000000 0000000 # xcffib [](https://github.com/tych0/xcffib/actions)
`xcffib` is intended to be a (mostly) drop-in replacement for `xpyb`, the original python binding for `xcb`.
## Installation
For most end users of software that depends on xcffib or developers writing
code against xcffib, you can use the version of xcffib on pypi. To install it,
you'll need libxcb's headers and libxcb-render's headers (these are available
via `sudo apt-get install libxcb-render0-dev` on Ubuntu). Once you have the C
headers installed, you can just `pip install xcffib`.
If you're interested in doing development, read on...
## Development dependencies
You should be able to install all the language deps from hackage or pip.
[.github/workflows/ci.yaml](https://github.com/tych0/xcffib/blob/master/.github/workflows/ci.yaml)
has an example of how to install the dependencies on Ubuntu flavors.
## Hacking
See the [Makefile](https://github.com/tych0/xcffib/blob/master/Makefile) for
examples on how to run the tests. Your contribution should at pass `make check`
before it can be merged. The `newtests` make target can be used to regenerate
expected haskell test data if the tests are failing because you made a change
to the generated python code.
### Hacking on new xcbproto versions
Sometimes (more often recently), xcbproto makes some updates that we need to
do some work for. These often require some updates to `xcb-types` as well.
First, hack your changes into `xcb-types` and `cabal install` them, then git
clone the version of xcbproto you want to somewhere, e.g. `~/packages`:
~/packages $ git clone https://gitlab.freedesktop.org/xorg/proto/xcbproto.git
Finally, you can build/test xcffib against this custom version of
`xcb-{proto|types}` with:
make XCBDIR=~/packages/xcbproto/src check
### Hacking on new xcb-types versions
To go along with new xcbproto elements, sometimes you need to hack on newer
versions of xcb-types. Newer cabals require you to do something like:
echo packages: ../xcb-types/xcb-types.cabal ./xcffib.cabal > cabal.project
In order to find locally modified versions of xcb-types.
## Differences
In general, you should `s/xcb/xcffib/g`. Explicit differences are listed below,
however I don't think these will prevent any porting, because these were either
not public APIs, or not actually generated (in the case of the exceptions) by
`xpyb`. I think most porting should Just Work via the regex above.
* `xcb.Exception` is spelled `xcffib.XcffibException` and is also a parent of
all exceptions generated by xcffib.
* `xcb.ConnectException` is gone, it was unused
* `xcffib.ConnectionException` is raised on connection errors
* `xcb.Iterator` is gone; similar functionality is implemented by
`xcffib.pack_list`.
* `xcb.Request` is gone. It was an entirely internal and unnecessary interface.
* `xcffib.Connection.send_request` takes slightly different (but more sensible)
arguments.
* Everywhere `xcbproto` says `char`, `xcffib` uses a char. That means on input
for a `
`, you can use a python string literal. `xcffib`
also gives you a string of length 1 out for each element in such a list,
instead of an `int`. Finally, there is a helper method called `to_string` on
`xcffib.List`, to convert these string-like things into native strings. This
means that for things like `xproto.STR`, you can just do
`the_str.name.to_string()` instead of `''.join(map(chr, the_str.name))`.
* As above, `void` is also packed/unpacked as `char`s, since the convention is
to use it as string data, e.g. in `xproto.ChangeProperty`.
* The submodule `xcb` is gone. The top module re-exported all these constants
anyway, so they live there now. i.e. `xcb.xcb.CurrentTime` is now just
`xcffib.CurrentTime`.
## Enhancements
* When sending requests with nested structs you no longer have to pack the
contents yourself. For example, when calling `xproto.FillPoly`, you used to
have to convert the `POINT`s you were passing in to some sort of buffer which
had them `struct.pack`'d. Now, you can just pass an iterable (or
`xcffib.List`) of `POINT`s and it will be automatically packed for you.
* Most of the lower level XCB connection primitives that were previously not
exposed are now available via `xcffib.{ffi,C}`, assuming you want to go out
of band of the binding.
* Checked vs. Unchecked requests are still supported (via Checked and Unchecked
function calls). However, there is also an additional optional parameter
`is_checked` to each request function, to allow you to set the checked status
that way. Additionally, requests that are (un)checked by default, e.g.
`QueryTree` (`CreateWindow`), have a `QueryTreeChecked`
(`CreateWindowUnchecked`) version which just has the same default behavior.
* The `FooError` `BadFoo` duality is gone; it was difficult to understand what
to actually catch if you wanted to handle an error. Instead, `FooError` and
`BadFoo` are aliases, and both implement the X error object description and
python Exception (via inheriting from `XcffibException`).
* You can now create synthetic events. This makes it much easier to work with
`ClientMessageEvent`s. For example:
```python
e = xcffib.xproto.ClientMessageEvent.synthetic(format=..., window=..., ...)
conn.core.SendEvent(..., e.pack())
```
## Why haskell?
Why is the binding generator written in haskell? Because haskell is awesome.
## TODO
* xprint and xkb support. These will require some non-trivial work in
xcb-types, since it won't parse them correctly.
xcffib-1.5.0/cabal.config 0000664 0000000 0000000 00000000121 14470210147 0015201 0 ustar 00root root 0000000 0000000 repository hackage.haskell.org
url: http://hackage.haskell.org/
secure: True
xcffib-1.5.0/generator/ 0000775 0000000 0000000 00000000000 14470210147 0014744 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/generator/Data/ 0000775 0000000 0000000 00000000000 14470210147 0015615 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/generator/Data/XCB/ 0000775 0000000 0000000 00000000000 14470210147 0016231 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/generator/Data/XCB/Python/ 0000775 0000000 0000000 00000000000 14470210147 0017512 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/generator/Data/XCB/Python/Parse.hs 0000664 0000000 0000000 00000104070 14470210147 0021122 0 ustar 00root root 0000000 0000000 {-
- Copyright 2014 Tycho Andersen
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
{-# LANGUAGE ViewPatterns #-}
module Data.XCB.Python.Parse (
parseXHeaders,
xform,
renderPy,
calcsize
) where
import Control.Applicative hiding (getConst)
import Control.Monad.State.Strict
import Data.Attoparsec.ByteString.Char8
import Data.Bits
import qualified Data.ByteString.Char8 as BS
import Data.Either.Combinators as EC
import Data.List
import qualified Data.Map as M
import Data.Tree
import Data.Maybe
import Data.XCB.FromXML
import Data.XCB.Types as X
import Data.XCB.Python.PyHelpers
import Language.Python.Common as P
import System.FilePath
import System.FilePath.Glob
import Text.Printf
data TypeInfo =
-- | A "base" X type, i.e. one described in baseTypeInfo; first arg is the
-- struct.unpack string, second is the size.
BaseType String |
-- | A composite type, i.e. a Struct or Union created by XCB. First arg is
-- the extension that defined it, second is the name of the type.
CompositeType String String
deriving (Eq, Ord, Show)
type TypeInfoMap = M.Map X.Type TypeInfo
data BindingPart =
Request (Statement ()) (Suite ()) |
Declaration (Suite ()) |
Noop
deriving (Show)
collectBindings :: [BindingPart] -> (Suite (), Suite ())
collectBindings = foldr collectR ([], [])
where
collectR :: BindingPart -> (Suite (), Suite ()) -> (Suite (), Suite ())
collectR (Request def decl) (defs, decls) = (def : defs, decl ++ decls)
collectR (Declaration decl) (defs, decls) = (defs, decl ++ decls)
collectR Noop x = x
parseXHeaders :: FilePath -> IO [XHeader]
parseXHeaders fp = do
files <- namesMatching $ fp > "*.xml"
fromFiles files
renderPy :: Suite () -> String
renderPy s = ((intercalate "\n") $ map prettyText s) ++ "\n"
-- | Generate the code for a set of X headers. Note that the code is generated
-- in dependency order, NOT in the order you pass them in. Thus, you get a
-- string (a suggested filename) along with the python code for that XHeader
-- back.
xform :: [XHeader] -> [(String, Suite ())]
xform = map buildPython . dependencyOrder
where
buildPython :: Tree XHeader -> (String, Suite ())
buildPython forest =
let forest' = (mapM processXHeader $ postOrder forest)
results = evalState forest' baseTypeInfo
in last results
processXHeader :: XHeader
-> State TypeInfoMap (String, Suite ())
processXHeader header = do
let imports = [mkImport "xcffib", mkImport "struct", mkImport "io"]
version = mkVersion header
key = maybeToList $ mkKey header
globals = [mkDict "_events", mkDict "_errors"]
name = xheader_header header
add = [mkAddExt header]
parts <- mapM (processXDecl name) $ xheader_decls header
let (requests, decls) = collectBindings parts
ext = if length requests > 0
then [mkClass (name ++ "Extension") "xcffib.Extension" requests]
else []
return $ (name, concat [imports, version, key, globals, decls, ext, add])
-- Rearrange the headers in dependency order for processing (i.e. put
-- modules which import others after the modules they import, so typedefs
-- are propogated appropriately).
dependencyOrder :: [XHeader] -> Forest XHeader
dependencyOrder headers = unfoldForest unfold $ map xheader_header headers
where
headerM = M.fromList $ map (\h -> (xheader_header h, h)) headers
unfold s = let h = headerM M.! s in (h, deps h)
deps :: XHeader -> [String]
deps = catMaybes . map matchImport . xheader_decls
matchImport :: XDecl -> Maybe String
matchImport (XImport n) = Just n
matchImport _ = Nothing
postOrder :: Tree a -> [a]
postOrder (Node e cs) = (concat $ map postOrder cs) ++ [e]
mkAddExt :: XHeader -> Statement ()
mkAddExt (xheader_header -> "xproto") =
flip StmtExpr () $ mkCall "xcffib._add_core" [ mkName "xprotoExtension"
, mkName "Setup"
, mkName "_events"
, mkName "_errors"
]
mkAddExt header =
let name = xheader_header header
in flip StmtExpr () $ mkCall "xcffib._add_ext" [ mkName "key"
, mkName (name ++ "Extension")
, mkName "_events"
, mkName "_errors"
]
-- | Information on basic X types.
baseTypeInfo :: TypeInfoMap
baseTypeInfo = M.fromList $
[ (UnQualType "CARD8", BaseType "B")
, (UnQualType "uint8_t", BaseType "B")
, (UnQualType "CARD16", BaseType "H")
, (UnQualType "uint16_t", BaseType "H")
, (UnQualType "CARD32", BaseType "I")
, (UnQualType "uint32_t", BaseType "I")
, (UnQualType "CARD64", BaseType "Q")
, (UnQualType "uint64_t", BaseType "Q")
, (UnQualType "INT8", BaseType "b")
, (UnQualType "int8_t", BaseType "b")
, (UnQualType "INT16", BaseType "h")
, (UnQualType "int16_t", BaseType "h")
, (UnQualType "INT32", BaseType "i")
, (UnQualType "int32_t", BaseType "i")
, (UnQualType "INT64", BaseType "q")
, (UnQualType "uint64_t", BaseType "q")
, (UnQualType "BYTE", BaseType "B")
, (UnQualType "BOOL", BaseType "B")
, (UnQualType "char", BaseType "c")
, (UnQualType "void", BaseType "c")
, (UnQualType "float", BaseType "f")
, (UnQualType "double", BaseType "d")
]
-- | Clone of python's struct.calcsize.
calcsize :: String -> Int
calcsize str = sum [fromMaybe 1 i * getSize c | (i, c) <- parseMembers str]
where
sizeM :: M.Map Char Int
sizeM = M.fromList [ ('c', 1)
, ('B', 1)
, ('b', 1)
, ('H', 2)
, ('h', 2)
, ('I', 4)
, ('i', 4)
, ('Q', 8)
, ('q', 8)
, ('f', 4)
, ('d', 8)
, ('x', 1)
]
getSize = (M.!) sizeM
parseMembers :: String -> [(Maybe Int, Char)]
parseMembers s = case parseOnly lang (BS.pack s) of
Left err -> error ("can't calcsize " ++ s ++ " " ++ err)
Right xs -> xs
lang = many $ (,) <$> optional decimal <*> (satisfy $ inClass $ M.keys sizeM)
xBinopToPyOp :: X.Binop -> P.Op ()
xBinopToPyOp X.Add = P.Plus ()
xBinopToPyOp X.Sub = P.Minus ()
xBinopToPyOp X.Mult = P.Multiply ()
xBinopToPyOp X.Div = P.FloorDivide ()
xBinopToPyOp X.And = P.BinaryAnd ()
xBinopToPyOp X.RShift = P.ShiftRight ()
xUnopToPyOp :: X.Unop -> P.Op ()
xUnopToPyOp X.Complement = P.Invert ()
xExpressionToNestedPyExpr :: (String -> String) -> XExpression -> Expr ()
xExpressionToNestedPyExpr acc (Op o e1 e2) =
Paren (xExpressionToPyExpr acc (Op o e1 e2)) ()
xExpressionToNestedPyExpr acc xexpr =
xExpressionToPyExpr acc xexpr
xExpressionToPyExpr :: (String -> String) -> XExpression -> Expr ()
xExpressionToPyExpr _ (Value i) = mkInt i
xExpressionToPyExpr _ (Bit i) = BinaryOp (ShiftLeft ()) (mkInt 1) (mkInt i) ()
xExpressionToPyExpr acc (FieldRef n) = mkName $ acc n
xExpressionToPyExpr _ (EnumRef (UnQualType enum) n) = mkName $ enum ++ "." ++ n
-- Currently xcb only uses unqualified types, not sure how qualtype should behave
xExpressionToPyExpr _ (EnumRef (QualType ext n) _) = mkName $ ext ++ "." ++ n
xExpressionToPyExpr acc (PopCount e) =
mkCall "xcffib.popcount" [xExpressionToPyExpr acc e]
-- http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt#n290
xExpressionToPyExpr acc (SumOf n) = mkCall "sum" [mkName $ acc n]
xExpressionToPyExpr acc (Op o e1 e2) =
let o' = xBinopToPyOp o
e1' = xExpressionToNestedPyExpr acc e1
e2' = xExpressionToNestedPyExpr acc e2
in BinaryOp o' e1' e2' ()
xExpressionToPyExpr acc (Unop o e) =
let o' = xUnopToPyOp o
e' = xExpressionToNestedPyExpr acc e
in Paren (UnaryOp o' e' ()) ()
xExpressionToPyExpr acc (ParamRef n) =
if n == "num_axes"
then mkName $ acc n
else error ("unsupported paramref " ++ n)
getConst :: XExpression -> Maybe Int
getConst (Value i) = Just i
getConst (Bit i) = Just $ bit i
getConst (Op o e1 e2) = do
c1 <- getConst e1
c2 <- getConst e2
return $ case o of
X.Add -> c1 + c2
X.Sub -> c1 - c2
X.Mult -> c1 * c2
X.Div -> c1 `quot` c2
X.And -> c1 .&. c2
X.RShift -> c1 `shift` c2
getConst (Unop o e) = do
c <- getConst e
return $ case o of
X.Complement -> complement c
getConst (PopCount e) = fmap popCount $ getConst e
getConst _ = Nothing
xEnumElemsToPyEnum :: (String -> String) -> [XEnumElem] -> [(String, Expr ())]
xEnumElemsToPyEnum accessor membs = reverse $ conv membs [] [0..]
where
exprConv = xExpressionToPyExpr accessor
conv :: [XEnumElem] -> [(String, Expr ())] -> [Int] -> [(String, Expr ())]
conv ((EnumElem name expr) : els) acc is =
let expr' = fromMaybe (mkInt (head is)) $ fmap exprConv expr
is' = dropWhile (<= (fromIntegral (int_value expr'))) is
acc' = (name, expr') : acc
in conv els acc' is'
conv [] acc _ = acc
-- Add the xcb_generic_{request,reply}_t structure data to the beginning of a
-- pack string. This is a little weird because both structs contain a one byte
-- pad which isn't at the end. If the first element of the request or reply is
-- a byte long, it takes that spot instead, and there is one less offset
addStructData :: String -> String -> String
addStructData prefix (c : cs) | c `elem` "Bbx" =
let result = maybePrintChar prefix c
in if result == prefix then result ++ (c : cs) else result ++ cs
addStructData prefix s = (maybePrintChar prefix 'x') ++ s
maybePrintChar :: String -> Char -> String
maybePrintChar s c | "%c" `isInfixOf` s = printf s c
maybePrintChar s _ = s
-- Don't prefix a single pad byte with a '1'. This is simpler to parse
-- visually, and also simplifies addStructData above.
mkPad :: Int -> String
mkPad 1 = "x"
mkPad i = (show i) ++ "x"
structElemToPyUnpack :: Expr ()
-> String
-> TypeInfoMap
-> GenStructElem Type
-> Either (Maybe String, String)
(String, Either (Expr (), Expr ())
([(Expr (), [GenStructElem Type])]), Maybe Int)
structElemToPyUnpack _ _ _ (Pad PadBytes i) = Left (Nothing, mkPad i)
structElemToPyUnpack _ _ _ (Pad PadAlignment _) = Left (Nothing, "")
-- XXX: This is a cheap hack for noop, we should really do better.
structElemToPyUnpack _ _ _ (Doc _ _ _) = Left (Nothing, "")
-- XXX: What does fd mean? we should implement it correctly
structElemToPyUnpack _ _ _ (Fd _) = Left (Nothing, "")
structElemToPyUnpack _ _ _ (Length _ _) = Left (Nothing, "")
-- The switch fields pick the way to expression to pack based on the expression
structElemToPyUnpack _ _ _ (Switch name expr _ bitcases) =
let cmp = xExpressionToPyExpr ((++) "self.") expr
switch = map (mkSwitch cmp) bitcases
in Right (name, Right switch, Nothing)
where
mkSwitch :: Expr ()
-> BitCase
-> (Expr (), [GenStructElem Type])
mkSwitch cmp (BitCase Nothing bcCmp _ elems) =
let cmpVal = xExpressionToPyExpr id bcCmp
equality = BinaryOp (P.BinaryAnd ()) cmp cmpVal ()
in (equality, elems)
mkSwitch cmp (BitCase (Just _) bcCmp _ elems) =
let cmpVal = xExpressionToPyExpr id bcCmp
equality = BinaryOp (P.Equality ()) cmp cmpVal ()
in (equality, elems)
-- The enum field is mostly for user information, so we ignore it.
structElemToPyUnpack unpacker ext m (X.List n typ len _) =
let attr = ((++) "self.")
len' = fromMaybe pyNone $ fmap (xExpressionToPyExpr attr) len
cons = case m M.! typ of
BaseType c -> mkStr c
CompositeType tExt c | ext /= tExt -> mkName $ tExt ++ "." ++ c
CompositeType _ "DeviceTimeCoord" ->
let wrapper = mkName "xcffib.__DeviceTimeCoord_wrapper"
in mkCall wrapper [mkName "DeviceTimeCoord", mkName (attr "num_axes")]
CompositeType _ c -> mkName c
list = mkCall "xcffib.List" [ unpacker
, cons
, len'
]
constLen = do
l <- len
getConst l
in Right (n, Left (list, cons), constLen)
-- The mask and enum fields are for user information, we can ignore them here.
structElemToPyUnpack unpacker ext m (SField n typ _ _) =
case m M.! typ of
BaseType c -> Left (Just n, c)
CompositeType tExt c ->
let c' = if tExt == ext then c else tExt ++ "." ++ c
field = mkCall c' [unpacker]
-- TODO: Ugh. Nothing here is wrong. Do we really need to carry the
-- length of these things around?
in Right (n, Left (field, mkName c'), Nothing)
structElemToPyUnpack _ _ _ (ExprField _ _ _) = error "Only valid for requests"
structElemToPyUnpack _ _ _ (ValueParam _ _ _ _) = error "Only valid for requests"
structElemToPyPack :: String
-> TypeInfoMap
-> (String -> String)
-> GenStructElem Type
-> Either (Maybe String, String) [(String, Either (Maybe (Expr ()))
[(Expr (), [GenStructElem Type])]
)]
structElemToPyPack _ _ _ (Pad _ i) = Left (Nothing, mkPad i)
-- TODO: implement these?
structElemToPyPack _ _ _ (Doc _ _ _) = Left (Nothing, "")
structElemToPyPack _ _ _ (Fd _) = Left (Nothing, "")
structElemToPyPack _ _ _ (Length _ _) = Left (Nothing, "")
structElemToPyPack _ _ accessor (Switch n expr _ bitcases) =
let name = accessor n
cmp = xExpressionToPyExpr accessor expr
elems = map (mkSwitch cmp) bitcases
in Right $ [(name, Right elems)]
where
mkSwitch :: Expr ()
-> BitCase
-> (Expr (), [GenStructElem Type])
mkSwitch cmp (BitCase _ bcCmp _ elems') =
let cmpVal = xExpressionToPyExpr accessor bcCmp
equality = BinaryOp (P.BinaryAnd ()) cmp cmpVal ()
in (equality, elems')
structElemToPyPack ext m accessor (SField n typ _ _) =
let name = accessor n
in case m M.! typ of
BaseType c -> Left (Just name, c)
CompositeType tExt typNam ->
let cond = mkCall "hasattr" [mkArg name, ArgExpr (mkStr "pack") ()]
trueB = mkCall (name ++ ".pack") noArgs
typNam' = if ext == tExt then typNam else tExt ++ "." ++ typNam
synthetic = mkCall (typNam' ++ ".synthetic") [mkArg ("*" ++ name)]
falseB = mkCall (mkDot synthetic "pack") noArgs
in Right $ [(name
, Left (Just (CondExpr trueB cond falseB ()))
)]
-- TODO: assert values are in enum?
structElemToPyPack ext m accessor (X.List n typ expr _) =
let name = accessor n
-- The convention seems to be either to have a nested in the
-- list, or use "%s_len" % name if there is no fieldref. We need to add
-- the _len to the arguments of the function but we don't need to pack
-- anything, which we denote using Nothing
list_len = if isNothing expr then [(name ++ "_len", Left Nothing)] else []
list = case m M.! typ of
BaseType c -> [(name
, Left (Just (mkCall "xcffib.pack_list" [ mkName $ name
, mkStr c
]))
)]
CompositeType tExt c ->
let c' = if tExt == ext then c else (tExt ++ "." ++ c)
in [(name
, Left (Just (mkCall "xcffib.pack_list" ([ mkName $ name
, mkName c'
])))
)]
in Right $ list_len ++ list
structElemToPyPack _ m accessor (ExprField name typ expr) =
let e = (xExpressionToPyExpr accessor) expr
name' = accessor name
in case m M.! typ of
BaseType c -> Right $ [(name'
, Left (Just (mkCall "struct.pack" [ mkStr ('=' : c)
, e
]))
)]
CompositeType _ _ -> Right $ [(name'
, Left (Just (mkCall (mkDot e "pack") noArgs))
)]
-- As near as I can tell here the padding param is unused.
structElemToPyPack _ m accessor (ValueParam typ mask _ list) =
case m M.! typ of
BaseType c ->
let mask' = mkCall "struct.pack" [mkStr ('=' : c), mkName $ accessor mask]
list' = mkCall "xcffib.pack_list" [ mkName $ accessor list
, mkStr "I"
]
in Right $ [(mask, Left (Just mask')), (list, Left (Just list'))]
CompositeType _ _ -> error (
"ValueParams other than CARD{16,32} not allowed.")
buf :: Suite ()
buf = [mkAssign "buf" (mkCall "io.BytesIO" noArgs)]
mkPackStmts :: String
-> String
-> TypeInfoMap
-> (String -> String)
-> String
-> [GenStructElem Type]
-> ([String], Suite ())
mkPackStmts ext name m accessor prefix membs =
let packF = structElemToPyPack ext m accessor
(toPack, stmts) = span EC.isLeft $ map packF membs
stmts' = map (either mkBasePack id) stmts
(args, keys) = let (as, ks) = unzip (map EC.fromLeft' toPack) in (catMaybes as, ks)
-- In some cases (e.g. xproto.ConfigureWindow) there is padding after
-- value_mask. The way the xml specification deals with this is by
-- specifying value_mask in both the regular pack location as well as
-- implying it implicitly. Thus, we want to make sure that if we've already
-- been told to pack something explcitly, that we don't also pack it
-- implicitly.
(listNames, listOrSwitches) = unzip $ filter (flip notElem args . fst) (concat stmts')
listWrites = concat $ map (uncurry mkWrites) $ zip listNames listOrSwitches
listNames' = case (ext, name) of
-- XXX: QueryTextExtents has a field named "odd_length"
-- which is unused, let's just drop it.
("xproto", "QueryTextExtents") ->
let notOdd "odd_length" = False
notOdd _ = True
in filter notOdd listNames
_ -> listNames
packStr = addStructData prefix $ intercalate "" keys
write = mkCall "buf.write" [mkCall "struct.pack"
(mkStr ('=' : packStr) : (map mkName args))]
writeStmt = if length packStr > 0 then [StmtExpr write ()] else []
in (args ++ listNames', writeStmt ++ listWrites)
where
mkWrites :: String
-> Either (Maybe (Expr ()))
[(Expr (), [GenStructElem Type])]
-> Suite ()
mkWrites _ (Left Nothing) = []
mkWrites _ (Left (Just expr)) = [mkListWrite expr]
mkWrites valueList (Right condList) =
let (conds, exprs) = unzip condList
(names, stmts) = unzip $ map (mkPackStmts ext name m accessor "") exprs
in map (\(x, y, z) -> Conditional [(x, map (mkPop valueList) y ++ z)] [] ()) $ zip3 conds names stmts
mkListWrite :: Expr ()
-> Statement ()
mkListWrite expr' = flip StmtExpr () . mkCall "buf.write" $ (: []) expr'
mkPop :: String
-> String
-> Statement ()
mkPop toPop n =
let pop = mkCall (mkDot toPop "pop") [mkInt 0]
in if null n then StmtExpr pop () else mkAssign n pop
mkBasePack (Nothing, "") = []
mkBasePack (n, c) =
let n' = maybe "" id n
in [(n', Left (Just (mkCall "struct.pack" [mkStr ('=' : c), mkName n'])))]
mkPackMethod :: String
-> String
-> TypeInfoMap
-> Maybe (String, Int)
-> [GenStructElem Type]
-> Maybe Int
-> Statement ()
mkPackMethod ext name m prefixAndOp structElems minLen =
let accessor = ((++) "self.")
(prefix, op) = case prefixAndOp of
Just ('x' : rest, i) ->
let packOpcode = mkCall "struct.pack" [mkStr "=B", mkInt i]
write = mkCall "buf.write" [packOpcode]
in (rest, [StmtExpr write ()])
Just (rest, _) -> error ("internal API error: " ++ show rest)
Nothing -> ("", [])
(_, packStmts) = mkPackStmts ext name m accessor prefix structElems
extend = concat $ do
len <- maybeToList minLen
let bufLen = mkName "buf_len"
bufLenAssign = mkAssign bufLen $ mkCall "len" [mkCall "buf.getvalue" noArgs]
test = (BinaryOp (LessThan ()) bufLen (mkInt len)) ()
bufWriteLen = Paren (BinaryOp (Minus ()) (mkInt 32) bufLen ()) ()
extra = mkCall "struct.pack" [repeatStr "x" bufWriteLen]
writeExtra = [StmtExpr (mkCall "buf.write" [extra]) ()]
return $ [bufLenAssign, mkIf test writeExtra]
ret = [mkReturn $ mkCall "buf.getvalue" noArgs]
in mkMethod "pack" (mkParams ["self"]) $ buf ++ op ++ packStmts ++ extend ++ ret
data StructUnpackState = StructUnpackState {
-- | stNeedsPad is whether or not a type_pad() is needed. As near
-- as I can tell the conditions are:
-- 1. a list was unpacked
-- 2. a struct was unpacked
-- ListFontsWithInfoReply is an example of a struct which has lots of
-- this type of thing.
stNeedsPad :: Bool,
-- The list of names the struct.pack accumulator has, and the
stNames :: [String],
-- The list of pack directives (potentially with a "%c" in it for
-- the prefix byte).
stPacks :: String
}
-- | Make a struct style (i.e. not union style) unpack.
mkStructStyleUnpack :: String
-> String
-> TypeInfoMap
-> [GenStructElem Type]
-> (Suite (), Maybe Int)
mkStructStyleUnpack prefix ext m membs =
let unpacked = map (structElemToPyUnpack (mkName "unpacker") ext m) membs
initial = StructUnpackState False [] prefix
(_, unpackStmts, size) = evalState (mkUnpackStmts unpacked) initial
base = [mkAssign "base" $ mkName "unpacker.offset"]
bufsize =
let rhs = BinaryOp (Minus ()) (mkName "unpacker.offset") (mkName "base") ()
in [mkAssign (mkAttr "bufsize") rhs]
statements = base ++ unpackStmts ++ bufsize
in (statements, size)
where
-- Apparently you only type_pad before unpacking Structs or Lists, never
-- base types.
mkUnpackStmts :: [Either (Maybe String, String)
(String, Either (Expr (), Expr ())
([(Expr (), [GenStructElem Type])]), Maybe Int)]
-> State StructUnpackState ([String], Suite (), Maybe Int)
mkUnpackStmts [] = flushAcc
mkUnpackStmts (Left (name, pack) : xs) = do
st <- get
let packs = if "%c" `isInfixOf` (stPacks st)
then addStructData (stPacks st) pack
else (stPacks st) ++ pack
put $ st { stNames = stNames st ++ maybeToList name
, stPacks = packs
}
mkUnpackStmts xs
mkUnpackStmts (Right (thisName, listOrSwitch, thisSz) : xs) = do
(packNames, packStmt, packSz) <- flushAcc
st <- get
put $ st { stNeedsPad = True }
let thisStmts = mkUnpackListOrSwitch thisName listOrSwitch (stNeedsPad st) st
(restNames, restStmts, restSz) <- mkUnpackStmts xs
let totalSize = do
before <- packSz
rest <- restSz
thisSz' <- thisSz
return $ before + rest + thisSz'
return ( packNames ++ [thisName] ++ restNames
, packStmt ++ thisStmts ++ restStmts
, totalSize
)
where
mkUnpackListOrSwitch :: String
-> Either (Expr (), Expr ())
([(Expr (), [GenStructElem Type])])
-> Bool
-> StructUnpackState
-> Suite ()
mkUnpackListOrSwitch name' (Left (list, cons)) needsPad _ =
let pad = if needsPad
then [typePad cons]
else []
in pad ++ [mkAssign (mkAttr name') list]
mkUnpackListOrSwitch _ (Right switchList) _ st' =
let (conds, elems) = unzip switchList
stmts = map (mkUnpackSwitchElems st') elems
in map (\x -> Conditional [x] [] ()) $ zip conds stmts
mkUnpackSwitchElems :: StructUnpackState
-> [GenStructElem Type]
-> Suite ()
mkUnpackSwitchElems st' elems' =
let unpacked' = map (structElemToPyUnpack (mkName "unpacker") ext m) elems'
(_, stmts', _) = evalState (mkUnpackStmts unpacked') st'
in stmts'
flushAcc :: State StructUnpackState ([String], Suite (), Maybe Int)
flushAcc = do
StructUnpackState needsPad args keys <- get
let size = calcsize keys
assign = mkUnpackFrom "unpacker" args keys
put $ StructUnpackState needsPad [] ""
return (args, assign, Just size)
typePad e = StmtExpr (mkCall "unpacker.pad" [e]) ()
-- | Given a (qualified) type name and a target type, generate a TypeInfoMap
-- updater.
mkModify :: String -> String -> TypeInfo -> TypeInfoMap -> TypeInfoMap
mkModify ext name ti m =
let m' = M.fromList [ (UnQualType name, ti)
, (QualType ext name, ti)
]
in M.union m m'
mkSyntheticMethod :: [GenStructElem Type] -> [Statement ()]
mkSyntheticMethod membs = do
let names = catMaybes $ map getName membs
args = mkParams $ "cls" : names
self = mkAssign "self" $ mkCall (mkDot "cls" "__new__") [mkName "cls"]
body = map assign names
ret = mkReturn $ mkName "self"
synthetic = mkMethod "synthetic" args $ (self : body) ++ [ret]
classmethod = Decorator [ident "classmethod"] noArgs ()
if null names then [] else [Decorated [classmethod] synthetic ()]
where
getName :: GenStructElem Type -> Maybe String
getName (Pad _ _) = Nothing
getName (X.List n _ _ _) = Just n
getName (SField n _ _ _) = Just n
getName (ExprField n _ _) = Just n
getName (ValueParam _ n _ _) = Just n
getName (Switch n _ _ _) = Just n
getName (Doc _ _ _) = Nothing
getName (Fd n) = Just n
getName (Length _ _) = Nothing
assign :: String -> Statement ()
assign n = mkAssign (mkDot "self" n) $ mkName n
processXDecl :: String
-> XDecl
-> State TypeInfoMap BindingPart
processXDecl ext (XTypeDef name typ) =
do modify $ \m -> mkModify ext name (m M.! typ) m
return Noop
processXDecl ext (XidType name) =
-- http://www.markwitmer.com/guile-xcb/doc/guile-xcb/XIDs.html
do modify $ mkModify ext name (BaseType "I")
return Noop
processXDecl _ (XImport n) =
return $ Declaration [ mkRelImport n]
processXDecl _ (XEnum name membs) =
return $ Declaration [mkEnum name $ xEnumElemsToPyEnum id membs]
processXDecl ext (XStruct n _ membs) = do
m <- get
let (statements, len) = mkStructStyleUnpack "" ext m membs
pack = mkPackMethod ext n m Nothing membs Nothing
synthetic = mkSyntheticMethod membs
fixedLength = maybeToList $ do
theLen <- len
let rhs = mkInt theLen
return $ mkAssign "fixed_size" rhs
modify $ mkModify ext n (CompositeType ext n)
return $ Declaration [mkXClass n "xcffib.Struct" False statements (pack : fixedLength ++ synthetic)]
processXDecl ext (XEvent name opcode _ xge membs noSequence) = do
m <- get
let cname = name ++ "Event"
prefix = if fromMaybe False noSequence then "x" else "x%c2x"
pack = mkPackMethod ext name m (Just (prefix, opcode)) membs (Just 32)
synthetic = mkSyntheticMethod membs
(statements, _) = mkStructStyleUnpack prefix ext m membs
eventsUpd = mkDictUpdate "_events" opcode cname
isxge = fromMaybe False xge
-- xgeexp = mkAssign "xge" (if fromMaybe False xge then (mkName "True") else (mkName "False"))
return $ Declaration [ mkXClass cname "xcffib.Event" isxge statements (pack : synthetic)
, eventsUpd
]
processXDecl ext (XError name opcode _ membs) = do
m <- get
let cname = name ++ "Error"
prefix = "xx2x"
pack = mkPackMethod ext name m (Just (prefix, opcode)) membs Nothing
(statements, _) = mkStructStyleUnpack prefix ext m membs
errorsUpd = mkDictUpdate "_errors" opcode cname
alias = mkAssign ("Bad" ++ name) (mkName cname)
return $ Declaration [ mkXClass cname "xcffib.Error" False statements [pack]
, alias
, errorsUpd
]
processXDecl ext (XRequest name opcode _ membs reply) = do
m <- get
let
-- xtest doesn't seem to use the same packing strategy as everyone else,
-- but there is no clear indication in the XML as to why that is. yay.
prefix = if ext /= "xproto" then "xx2x" else "x%c2x"
(args, packStmts) = mkPackStmts ext name m id prefix membs
cookieName = (name ++ "Cookie")
replyDecl = concat $ maybeToList $ do
GenXReply _ reply' <- reply
let (replyStmts, _) = mkStructStyleUnpack "x%c2x4x" ext m reply'
replyName = name ++ "Reply"
theReply = mkXClass replyName "xcffib.Reply" False replyStmts []
replyType = mkAssign "reply_type" $ mkName replyName
cookie = mkClass cookieName "xcffib.Cookie" [replyType]
return [theReply, cookie]
hasReply = if length replyDecl > 0
then [ArgExpr (mkName cookieName) ()]
else []
isChecked = pyTruth $ isJust reply
argChecked = ArgKeyword (ident "is_checked") (mkName "is_checked") ()
checkedParam = Param (ident "is_checked") Nothing (Just isChecked) ()
allArgs = (mkParams $ "self" : (filter (not . null) args)) ++ [checkedParam]
mkArg' = flip ArgExpr ()
ret = mkReturn $ mkCall "self.send_request" ((map mkArg' [ mkInt opcode
, mkName "buf"
])
++ hasReply
++ [argChecked])
requestBody = buf ++ packStmts ++ [ret]
request = mkMethod name allArgs requestBody
return $ Request request replyDecl
processXDecl ext (XUnion name _ membs) = do
m <- get
let unpackF = structElemToPyUnpack unpackerCopy ext m
(fields, listInfo) = span EC.isLeft $ map unpackF membs
toUnpack = concat $ map (mkUnionUnpack . EC.fromLeft') fields
listInfo' = map (either mkBaseUnpack id) listInfo
(names, listOrSwitches, _) = unzip3 listInfo'
(exprs, _) = unzip $ map EC.fromLeft' listOrSwitches
lists = map (uncurry mkAssign) $ zip (map mkAttr names) exprs
initMethod = lists ++ toUnpack
-- Here, we only want to pack the first member of the union, since every
-- member is the same data and we don't want to repeatedly pack it.
pack = mkPackMethod ext name m Nothing [head membs] Nothing
decl = [mkXClass name "xcffib.Union" False initMethod [pack]]
modify $ mkModify ext name (CompositeType ext name)
return $ Declaration decl
where
unpackerCopy = mkCall "unpacker.copy" noArgs
mkUnionUnpack :: (Maybe String, String)
-> Suite ()
mkUnionUnpack (n, typ) =
mkUnpackFrom unpackerCopy (maybeToList n) typ
mkBaseUnpack _ = error "xcffib: trailing base types unpack not implemented"
processXDecl ext (XidUnion name _) =
-- These are always unions of only XIDs.
do modify $ mkModify ext name (BaseType "I")
return Noop
-- EventStruct basically describes a set of possible events that could be
-- represented by this one member. Slated to land in 1.13, it is only used in
-- SendExtensionEvent for now.
--
-- Rather than do a bunch of work nobody will use, I've punted on this for now,
-- leaving EventStructs as raw buffers. Since we support synthetic creation of
-- events from buffers and SendExtensionEvent has the event types, people can
-- unpack the thing themselves, by using the raw buffer that we keep around in
-- the new Buffer class. Maybe some day in the future someone can add some
-- syntactic sugar to make this a little nicer, but at least things compile
-- again.
processXDecl ext (XEventStruct name _) = do
modify $ mkModify ext name (CompositeType ext name)
return $ Declaration $ [mkXClass name "xcffib.Buffer" False [] []]
mkVersion :: XHeader -> Suite ()
mkVersion header =
let major = ver "MAJOR_VERSION" (xheader_major_version header)
minor = ver "MINOR_VERSION" (xheader_minor_version header)
in major ++ minor
where
ver :: String -> Maybe Int -> Suite ()
ver target i = maybeToList $ fmap (\x -> mkAssign target (mkInt x)) i
mkKey :: XHeader -> Maybe (Statement ())
mkKey header = do
name <- xheader_xname header
let call = mkCall "xcffib.ExtensionKey" [mkStr name]
return $ mkAssign "key" call
xcffib-1.5.0/generator/Data/XCB/Python/PyHelpers.hs 0000664 0000000 0000000 00000014240 14470210147 0021762 0 ustar 00root root 0000000 0000000 {-
- Copyright 2014 Tycho Andersen
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
module Data.XCB.Python.PyHelpers (
mkImport,
mkRelImport,
mkInt,
mkAssign,
mkCall,
noArgs,
mkArg,
mkEnum,
mkName,
mkDot,
mkAttr,
mkIncr,
mkClass,
mkEmptyClass,
mkXClass,
mkStr,
mkUnpackFrom,
mkDict,
mkDictUpdate,
mkMethod,
mkReturn,
pyTruth,
mkParams,
ident,
pyNone,
mkIf,
repeatStr
) where
import Data.List.Split
import Data.Maybe
import Language.Python.Common
_reserved :: [String]
_reserved = [ "None"
, "def"
, "class"
, "and"
, "or"
]
class PseudoExpr a where
getExpr :: a -> Expr ()
instance PseudoExpr String where
getExpr s = mkName s
instance PseudoExpr (Expr ()) where
getExpr = id
-- | Create and sanatize a python identifier.
ident :: String -> Ident ()
ident s | s `elem` _reserved = Ident ("_" ++ s) ()
ident s | isInt s = Ident ("_" ++ s) ()
where
isInt str = isJust $ ((maybeRead str) :: Maybe Int)
maybeRead = fmap fst . listToMaybe . reads
ident s = Ident s ()
-- Make a DottedName out of a string like "foo.bar" for use in imports.
mkDottedName :: String -> DottedName ()
mkDottedName = map ident . splitOn "."
mkVar :: String -> Expr ()
mkVar name = Var (ident name) ()
-- | Make an Expr out of a string like "foo.bar" describing the name.
mkName :: String -> Expr ()
mkName s =
let strings = splitOn "." s
in foldl mkDot (mkVar $ head strings) (tail strings)
mkDot :: PseudoExpr a => a -> String -> Expr ()
mkDot e1 attr = Dot (getExpr e1) (ident attr) ()
-- | Make an attribute access, i.e. self..
mkAttr :: String -> Expr ()
mkAttr s = mkName ("self." ++ s)
mkImport :: String -> Statement ()
mkImport name = Import [ImportItem (mkDottedName name) Nothing ()] ()
mkRelImport :: String -> Statement ()
mkRelImport name = FromImport (ImportRelative 1 Nothing ()) (FromItems [FromItem (ident name) Nothing ()] ()) ()
mkInt :: Int -> Expr ()
mkInt i = Int (toInteger i) (show i) ()
mkAssign :: PseudoExpr a => a -> Expr () -> Statement ()
mkAssign name expr = Assign [getExpr name] expr ()
mkIncr :: String -> Expr () -> Statement ()
mkIncr name expr = AugmentedAssign (mkName name) (PlusAssign ()) expr ()
class PseudoArgument a where
getArgument :: a -> Argument ()
instance PseudoArgument (Expr ()) where
getArgument p = ArgExpr p ()
instance PseudoArgument (Argument ()) where
getArgument = id
mkCall :: (PseudoExpr a, PseudoArgument b) => a -> [b] -> Expr ()
mkCall name args = Call (getExpr name) (map getArgument args) ()
noArgs :: [Argument ()]
noArgs = []
mkEnum :: String -> [(String, Expr ())] -> Statement ()
mkEnum cname values =
let body = map (uncurry mkAssign) values
in Class (Ident cname ()) [] body ()
mkParams :: [String] -> [Parameter ()]
mkParams = map (\x -> Param (ident x) Nothing Nothing ())
mkArg :: String -> Argument ()
mkArg n = ArgExpr (mkName n) ()
mkXClass :: String -> String -> Bool -> Suite () -> Suite () -> Statement ()
mkXClass clazz superclazz False [] [] = mkEmptyClass clazz superclazz
mkXClass clazz superclazz xge constructor methods =
let args = [ "self", "unpacker" ]
super = mkCall (superclazz ++ ".__init__") $ map mkName args
body = eventToUnpacker : (StmtExpr super ()) : constructor
initParams = mkParams args
xgeexp = mkAssign "xge" (if xge then (mkName "True") else (mkName "False"))
initMethod = Fun (ident "__init__") initParams Nothing body ()
in mkClass clazz superclazz $ xgeexp : initMethod : methods
where
-- In some cases (e.g. when creating ClientMessageEvents), our events are
-- passed directly to __init__. Since we don't keep track of the
-- underlying buffers after the event is created, we have to re-pack
-- things so they can be unpacked again.
eventToUnpacker :: Statement ()
eventToUnpacker = let newUnpacker = mkAssign "unpacker" (mkCall "xcffib.MemoryUnpacker"
[mkCall "unpacker.pack" noArgs])
cond = mkCall "isinstance" [mkName "unpacker", mkName "xcffib.Protobj"]
in mkIf cond [newUnpacker]
mkEmptyClass :: String -> String -> Statement ()
mkEmptyClass clazz superclazz = mkClass clazz superclazz [Pass ()]
mkClass :: String -> String -> Suite () -> Statement ()
mkClass clazz superclazz body = Class (ident clazz) [mkArg superclazz] body ()
mkStr :: String -> Expr ()
mkStr s = Strings ["\"", s, "\""] ()
mkTuple :: [Expr ()] -> Expr ()
mkTuple = flip Tuple ()
mkUnpackFrom :: PseudoExpr a => a -> [String] -> String -> Suite ()
mkUnpackFrom unpacker names packs =
let lhs = mkTuple $ map mkAttr names
-- Don't spam with this default arg unless it is really necessary.
unpackF = mkDot unpacker "unpack"
rhs = mkCall unpackF [mkStr packs]
stmt = if length names > 0 then mkAssign lhs rhs else StmtExpr rhs ()
in if length packs > 0 then [stmt] else []
mkDict :: String -> Statement ()
mkDict name = mkAssign name (Dictionary [] ())
mkDictUpdate :: String -> Int -> String -> Statement ()
mkDictUpdate dict key value =
mkAssign (Subscript (mkName dict) (mkInt key) ()) (mkName value)
mkMethod :: String -> [Parameter ()] -> Suite () -> Statement ()
mkMethod name args body = Fun (ident name) args Nothing body ()
mkReturn :: Expr () -> Statement ()
mkReturn = flip Return () . Just
pyTruth :: Bool -> Expr ()
pyTruth = flip Bool ()
pyNone :: Expr ()
pyNone = None ()
mkIf :: Expr () -> Suite () -> Statement ()
mkIf e s = Conditional [(e, s)] [] ()
repeatStr :: String -> Expr () -> Expr ()
repeatStr s i = BinaryOp (Multiply ()) (mkStr s) i ()
xcffib-1.5.0/generator/xcffibgen.hs 0000664 0000000 0000000 00000003647 14470210147 0017245 0 ustar 00root root 0000000 0000000 {-
- Copyright 2014 Tycho Andersen
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
module Main where
import Data.XCB.Types
import Data.XCB.Python.Parse
import Options.Applicative
import System.Directory
import System.FilePath
data Xcffibgen = Xcffibgen { input :: String
, output :: String
}
options :: Parser Xcffibgen
options = Xcffibgen
<$> strOption
( long "input"
<> metavar "DIR"
<> help "Input directory containing xcb xml files.")
<*> strOption
( long "output"
<> metavar "DIR"
<> help "Output directory for generated python.")
-- Headers we can't emit right now. Obviously we want to get rid of this :-)
badHeaders :: [String]
badHeaders = [ "xkb"
, "xprint"
]
run :: Xcffibgen -> IO ()
run (Xcffibgen inp out) = do
headers <- parseXHeaders inp
let headers' = filter (flip notElem badHeaders . xheader_header) headers
createDirectoryIfMissing True out
sequence_ $ map processFile $ xform headers'
where
processFile (fname, suite) = do
putStrLn fname
let fname' = out > fname ++ ".py"
contents = renderPy suite
writeFile fname' contents
main :: IO ()
main = execParser opts >>= run
where
opts = info (helper <*> options)
( fullDesc
<> progDesc "Generate XCB bindings for python."
<> header "xcffib - the cffi-based XCB generator")
xcffib-1.5.0/module/ 0000775 0000000 0000000 00000000000 14470210147 0014243 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/module/__init__.py 0000664 0000000 0000000 00000064745 14470210147 0016374 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
# Copyright 2014 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division
import ctypes.util
import functools
import io
import platform
import struct
import weakref
from .ffi import ffi
if platform.system() == "Darwin":
soname = "libxcb.dylib"
elif platform.system() == "Windows":
soname = "libxcb.dll"
else:
soname = ctypes.util.find_library("xcb")
if soname is None:
soname = "libxcb.so"
lib = ffi.dlopen(soname)
__xcb_proto_version__ = 'placeholder'
__version__ = 'placeholder'
X_PROTOCOL = lib.X_PROTOCOL
X_PROTOCOL_REVISION = lib.X_PROTOCOL_REVISION
XCB_NONE = lib.XCB_NONE
XCB_COPY_FROM_PARENT = lib.XCB_COPY_FROM_PARENT
XCB_CURRENT_TIME = lib.XCB_CURRENT_TIME
XCB_NO_SYMBOL = lib.XCB_NO_SYMBOL
# For xpyb compatibility
NONE = XCB_NONE
CopyFromParent = XCB_COPY_FROM_PARENT
CurrentTime = XCB_CURRENT_TIME
NoSymbol = XCB_NO_SYMBOL
XCB_CONN_ERROR = lib.XCB_CONN_ERROR
XCB_CONN_CLOSED_EXT_NOTSUPPORTED = lib.XCB_CONN_CLOSED_EXT_NOTSUPPORTED
XCB_CONN_CLOSED_MEM_INSUFFICIENT = lib.XCB_CONN_CLOSED_MEM_INSUFFICIENT
XCB_CONN_CLOSED_REQ_LEN_EXCEED = lib.XCB_CONN_CLOSED_REQ_LEN_EXCEED
XCB_CONN_CLOSED_PARSE_ERR = lib.XCB_CONN_CLOSED_PARSE_ERR
XCB_CONN_CLOSED_INVALID_SCREEN = lib.XCB_CONN_CLOSED_INVALID_SCREEN
XCB_CONN_CLOSED_FDPASSING_FAILED = lib.XCB_CONN_CLOSED_FDPASSING_FAILED
cffi_explicit_lifetimes = weakref.WeakKeyDictionary()
def type_pad(t, i):
return -i & (3 if t > 4 else t - 1)
def visualtype_to_c_struct(vt):
# let ffi be a kwarg so cairocffi can pass in its ffi
# cfficairo needs an xcb_visualtype_t
s = ffi.new("struct xcb_visualtype_t *")
s.visual_id = vt.visual_id
s._class = vt._class
s.bits_per_rgb_value = vt.bits_per_rgb_value
s.colormap_entries = vt.colormap_entries
s.red_mask = vt.red_mask
s.green_mask = vt.green_mask
s.blue_mask = vt.blue_mask
return s
class Unpacker(object):
def __init__(self, known_max=None):
self.size = 0
self.offset = 0
self.known_max = known_max
if self.known_max is not None:
self._resize(known_max)
def pad(self, thing):
if isinstance(thing, type) and issubclass(thing, (Struct, Union)):
if hasattr(thing, "fixed_size"):
size = thing.fixed_size
else:
size = 4
else:
size = struct.calcsize(thing)
self.offset += type_pad(size, self.offset)
def unpack(self, fmt, increment=True):
fmt = "=" + fmt
size = struct.calcsize(fmt)
if size > self.size - self.offset:
self._resize(size)
ret = struct.unpack_from(fmt, self.buf, self.offset)
if increment:
self.offset += size
return ret
def cast(self, typ):
assert self.offset == 0
return ffi.cast(typ, self.cdata)
def copy(self):
raise NotImplementedError
@classmethod
def synthetic(cls, data, format):
self = cls.__new__(cls)
self.__init__(len(data))
self.buf = data
self.offset = 0
self.size = len(data)
return self
class CffiUnpacker(Unpacker):
def __init__(self, cdata, known_max=None):
self.cdata = cdata
Unpacker.__init__(self, known_max)
def _resize(self, increment):
if self.offset + increment > self.size:
if self.known_max is not None:
assert self.size + increment <= self.known_max
self.size = self.offset + increment
self.buf = ffi.buffer(self.cdata, self.size)
def copy(self):
new = CffiUnpacker(self.cdata, self.known_max)
new.offset = self.offset
new.size = self.size
return new
class MemoryUnpacker(Unpacker):
def __init__(self, buf):
self.buf = buf
Unpacker.__init__(self, len(self.buf))
def _resize(self, increment):
if self.size + increment > self.known_max:
raise XcffibException("resizing memory buffer to be too big")
self.size += increment
def copy(self):
new = MemoryUnpacker(self.buf)
new.offset = self.offset
new.size = self.size
return new
def popcount(n):
return bin(n).count('1')
class XcffibException(Exception):
""" Generic XcbException; replaces xcb.Exception. """
pass
class ConnectionException(XcffibException):
REASONS = {
lib.XCB_CONN_ERROR: (
'xcb connection errors because of socket, '
'pipe and other stream errors.'),
lib.XCB_CONN_CLOSED_EXT_NOTSUPPORTED: (
'xcb connection shutdown because extension not supported'),
lib.XCB_CONN_CLOSED_MEM_INSUFFICIENT: (
'malloc(), calloc() and realloc() error upon failure, '
'for eg ENOMEM'),
lib.XCB_CONN_CLOSED_REQ_LEN_EXCEED: (
'Connection closed, exceeding request length that server '
'accepts.'),
lib.XCB_CONN_CLOSED_PARSE_ERR: (
'Connection closed, error during parsing display string.'),
lib.XCB_CONN_CLOSED_INVALID_SCREEN: (
'Connection closed because the server does not have a screen '
'matching the display.'),
lib.XCB_CONN_CLOSED_FDPASSING_FAILED: (
'Connection closed because some FD passing operation failed'),
}
def __init__(self, err):
XcffibException.__init__(
self, self.REASONS.get(err, "Unknown connection error."))
class ProtocolException(XcffibException):
pass
core = None
core_events = None
core_errors = None
# we use _setup here instead of just setup because of a nose bug that triggers
# when doing the packaging builds in debian:
# https://code.google.com/p/python-nose/issues/detail?id=326
_setup = None
extensions = {}
# This seems a bit over engineered to me; it seems unlikely there will ever be
# a core besides xproto, so why not just hardcode that?
def _add_core(value, __setup, events, errors):
if not issubclass(value, Extension):
raise XcffibException(
"Extension type not derived from xcffib.Extension")
if not issubclass(__setup, Struct):
raise XcffibException("Setup type not derived from xcffib.Struct")
global core
global core_events
global core_errors
global _setup
core = value
core_events = events
core_errors = errors
_setup = __setup
def _add_ext(key, value, events, errors):
if not issubclass(value, Extension):
raise XcffibException(
"Extension type not derived from xcffib.Extension")
extensions[key] = (value, events, errors)
class ExtensionKey(object):
""" This definitely isn't needed, but we keep it around for compatibility
with xpyb.
"""
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, o):
return self.name == o.name
def __ne__(self, o):
return self.name != o.name
def to_cffi(self):
c_key = ffi.new("struct xcb_extension_t *")
c_key.name = name = ffi.new('char[]', self.name.encode())
cffi_explicit_lifetimes[c_key] = name
# xpyb doesn't ever set global_id, which seems wrong, but whatever.
c_key.global_id = 0
return c_key
class Protobj(object):
""" Note: Unlike xcb.Protobj, this does NOT implement the sequence
protocol. I found this behavior confusing: Protobj would implement the
sequence protocol on self.buf, and then List would go and implement it on
List.
Instead, when we need to create a new event from an existing event, we
repack that event into a MemoryUnpacker and use that instead (see
eventToUnpacker in the generator for more info.)
"""
def __init__(self, unpacker):
"""
Params:
- unpacker: an Unpacker object
"""
# if we don't know the size right now, we expect it to be calculated
# based on stuff in the structure, so we don't save it right now.
if unpacker.known_max is not None:
self.bufsize = unpacker.known_max
class Buffer(Protobj):
def __init__(self, unpacker):
Protobj.__init__(self, unpacker)
self.buf = unpacker.buf
class Struct(Protobj):
pass
class Union(Protobj):
@classmethod
def synthetic(cls, data=[], fmt=""):
self = cls.__new__(cls)
self.__init__(MemoryUnpacker(struct.pack(fmt, *data)))
return self
class Cookie(object):
reply_type = None
def __init__(self, conn, sequence, is_checked):
self.conn = conn
self.sequence = sequence
self.is_checked = is_checked
def reply(self):
data = self.conn.wait_for_reply(self.sequence)
return self.reply_type(data)
def check(self):
# Request is not void and checked.
assert self.is_checked and self.reply_type is None, (
"Request is not void and checked")
self.conn.request_check(self.sequence)
def discard_reply(self):
return self.conn.discard_reply(self.sequence)
class VoidCookie(Cookie):
def reply(self):
raise XcffibException("No reply for this message type")
class Extension(object):
def __init__(self, conn, key=None):
self.conn = conn
if key is None:
self.c_key = ffi.NULL
else:
c_key = key.to_cffi()
cffi_explicit_lifetimes[self] = c_key
self.c_key = c_key
def send_request(self, opcode, data, cookie=VoidCookie, reply=None,
is_checked=False):
data = data.getvalue()
assert len(data) > 3, "xcb_send_request data must be ast least 4 bytes"
xcb_req = ffi.new("xcb_protocol_request_t *")
xcb_req.count = 2
xcb_req.ext = self.c_key
xcb_req.opcode = opcode
xcb_req.isvoid = issubclass(cookie, VoidCookie)
# XXX: send_request here will use the memory *before* the passed in
# xcb_parts pointer in some cases, so we need to allocate some for it
# to use, although we don't use it ourselves.
#
# http://lists.freedesktop.org/archives/xcb/2014-February/009307.html
xcb_parts = ffi.new("struct iovec[4]")
# Here we need this iov_base to keep this memory alive until the end of
# the function.
xcb_parts[2].iov_base = iov_base = ffi.new('char[]', data) # noqa
xcb_parts[2].iov_len = len(data)
xcb_parts[3].iov_base = ffi.NULL
xcb_parts[3].iov_len = -len(data) & 3 # is this really necessary?
flags = lib.XCB_REQUEST_CHECKED if is_checked else 0
seq = self.conn.send_request(flags, xcb_parts + 2, xcb_req)
return cookie(self.conn, seq, is_checked)
def __getattr__(self, name):
if name.endswith("Checked"):
real = name[:-len("Checked")]
is_checked = True
elif name.endswith("Unchecked"):
real = name[:-len("Unchecked")]
is_checked = False
else:
raise AttributeError(name)
real = getattr(self, real)
return functools.partial(real, is_checked=is_checked)
class List(Protobj):
def __init__(self, unpacker, typ, count=None):
Protobj.__init__(self, unpacker)
self.list = []
old = unpacker.offset
if isinstance(typ, str):
self.list = list(unpacker.unpack("%d%s" % (count, typ)))
elif count is not None:
for _ in range(count):
item = typ(unpacker)
self.list.append(item)
else:
assert unpacker.known_max is not None
while unpacker.offset < unpacker.known_max:
item = typ(unpacker)
self.list.append(item)
self.bufsize = unpacker.offset - old
self.raw = bytes(unpacker.buf[old:old + self.bufsize])
assert count is None or count == len(self.list)
def __str__(self):
return str(self.list)
def __len__(self):
return len(self.list)
def __iter__(self):
return iter(self.list)
def __getitem__(self, key):
return self.list[key]
def __setitem__(self, key, value):
self.list[key] = value
def __delitem__(self, key):
del self.list[key]
def to_string(self):
""" A helper for converting a List of chars to a native string. Dies if
the list contents are not something that could be reasonably converted
to a string. """
try:
return ''.join(chr(i[0]) for i in self)
except TypeError:
return ''.join(chr(i) for i in self)
def to_utf8(self):
return b''.join(self).decode('utf-8')
def to_atoms(self):
""" A helper for converting a List of chars to an array of atoms """
return struct.unpack("<" + "%dI" % (len(self) // 4), b''.join(self))
def buf(self):
return self.raw
@classmethod
def synthetic(cls, list=None):
if list is None:
list = []
self = cls.__new__(cls)
self.list = list[:]
return self
class OffsetMap(object):
def __init__(self, core):
self.offsets = [(0, 0, core)]
def add(self, offset, opcode, things):
self.offsets.append((offset, opcode, things))
self.offsets.sort(key=lambda x: x[0], reverse=True)
def get_extension_item(self, extension, item):
try:
_, _, things = next((k, opcode, v) for k, opcode, v in self.offsets if opcode == extension)
return things[item]
except StopIteration:
raise IndexError(item)
def __getitem__(self, item):
try:
offset, _, things = next((k, opcode, v) for k, opcode, v in self.offsets if item >= k)
return things[item - offset]
except StopIteration:
raise IndexError(item)
class Connection(object):
""" `auth` here should be ':', a format bequeathed to us from
xpyb. """
def __init__(self, display=None, fd=-1, auth=None):
if auth is not None:
[name, data] = auth.split(b':')
c_auth = ffi.new("xcb_auth_info_t *")
c_auth.name = ffi.new('char[]', name)
c_auth.namelen = len(name)
c_auth.data = ffi.new('char[]', data)
c_auth.datalen = len(data)
else:
c_auth = ffi.NULL
if display is None:
display = ffi.NULL
else:
display = display.encode('latin1')
i = ffi.new("int *")
if fd > 0:
self._conn = lib.xcb_connect_to_fd(fd, c_auth)
elif c_auth != ffi.NULL:
self._conn = lib.xcb_connect_to_display_with_auth_info(display, c_auth, i)
else:
self._conn = lib.xcb_connect(display, i)
self.pref_screen = i[0]
self.invalid()
self._init_x()
def _init_x(self):
if core is None:
raise XcffibException("No core protocol object has been set. "
"Did you import xcffib.xproto?")
self.core = core(self)
self.setup = self.get_setup()
self._event_offsets = OffsetMap(core_events)
self._error_offsets = OffsetMap(core_errors)
self._setup_extensions()
def _setup_extensions(self):
for key, (_, events, errors) in extensions.items():
# We're explicitly not putting this as an argument to the next call
# as a hack for lifetime management.
c_ext = key.to_cffi()
reply = lib.xcb_get_extension_data(self._conn, c_ext)
self._event_offsets.add(reply.first_event, reply.major_opcode, events)
self._error_offsets.add(reply.first_error, reply.major_opcode, errors)
def __call__(self, key):
return extensions[key][0](self, key)
def invalid(self):
if self._conn is None:
raise XcffibException("Invalid connection.")
err = lib.xcb_connection_has_error(self._conn)
if err > 0:
raise ConnectionException(err)
def ensure_connected(f):
"""
Check that the connection is valid both before and
after the function is invoked.
"""
@functools.wraps(f)
def wrapper(*args):
self = args[0]
self.invalid()
try:
return f(*args)
finally:
self.invalid()
return wrapper
@ensure_connected
def get_setup(self):
self._setup = lib.xcb_get_setup(self._conn)
# No idea where this 8 comes from either, similar complate to the
# sizeof(xcb_generic_reply_t) below.
buf = CffiUnpacker(self._setup, known_max=8 + self._setup.length * 4)
return _setup(buf)
@ensure_connected
def get_screen_pointers(self):
"""
Returns the xcb_screen_t for every screen
useful for other bindings
"""
root_iter = lib.xcb_setup_roots_iterator(self._setup)
screens = [root_iter.data]
for i in range(self._setup.roots_len - 1):
lib.xcb_screen_next(ffi.addressof((root_iter)))
screens.append(root_iter.data)
return screens
@ensure_connected
def wait_for_event(self):
e = lib.xcb_wait_for_event(self._conn)
e = ffi.gc(e, lib.free)
self.invalid()
return self.hoist_event(e)
@ensure_connected
def poll_for_event(self):
e = lib.xcb_poll_for_event(self._conn)
self.invalid()
if e != ffi.NULL:
return self.hoist_event(e)
else:
return None
def has_error(self):
return lib.xcb_connection_has_error(self._conn)
@ensure_connected
def get_file_descriptor(self):
return lib.xcb_get_file_descriptor(self._conn)
@ensure_connected
def get_maximum_request_length(self):
return lib.xcb_get_maximum_request_length(self._conn)
@ensure_connected
def prefetch_maximum_request_length(self):
return lib.xcb_prefetch_maximum_request_length(self._conn)
@ensure_connected
def flush(self):
return lib.xcb_flush(self._conn)
@ensure_connected
def generate_id(self):
return lib.xcb_generate_id(self._conn)
def disconnect(self):
if self._conn is not None:
lib.xcb_disconnect(self._conn)
self._conn = None
def _process_error(self, c_error):
self.invalid()
if c_error != ffi.NULL:
error = self._error_offsets[c_error.error_code]
buf = CffiUnpacker(c_error)
raise error(buf)
@ensure_connected
def wait_for_reply(self, sequence):
error_p = ffi.new("xcb_generic_error_t **")
data = lib.xcb_wait_for_reply(self._conn, sequence, error_p)
data = ffi.gc(data, lib.free)
try:
self._process_error(error_p[0])
finally:
if error_p[0] != ffi.NULL:
lib.free(error_p[0])
if data == ffi.NULL:
# No data and no error => bad sequence number
raise XcffibException("Bad sequence number %d" % sequence)
reply = ffi.cast("xcb_generic_reply_t *", data)
# this is 32 and not `sizeof(xcb_generic_reply_t) == 8` because,
# according to the X11 protocol specs: "Every reply consists of 32 bytes
# followed by zero or more additional bytes of data, as specified in the
# length field."
return CffiUnpacker(data, known_max=32 + reply.length * 4)
@ensure_connected
def request_check(self, sequence):
cookie = ffi.new("xcb_void_cookie_t [1]")
cookie[0].sequence = sequence
err = lib.xcb_request_check(self._conn, cookie[0])
self._process_error(err)
def hoist_event(self, e):
""" Hoist an xcb_generic_event_t to the right xcffib structure. """
if e.response_type == 0:
return self._process_error(ffi.cast("xcb_generic_error_t *", e))
# We mask off the high bit here because events sent with SendEvent have
# this bit set. We don't actually care where the event came from, so we
# just throw this away. Maybe we could expose this, if anyone actually
# cares about it.
response_type = e.response_type & 0x7f
buf = CffiUnpacker(e)
event = None
# If the response is a GeGenericEvent then we can try to hoist this to relevant
# extension event.
if response_type == 35:
# Extract the extension opcode and event
extension, _, event_type, _ = buf.unpack("xB2xIH22xI", increment=False)
try:
# Try to find matching event. If this fails, an IndexError is raised and
# we'll fall back to raising an GeGenericEvent
event = self._event_offsets.get_extension_item(extension, event_type)
except IndexError:
pass
if event is None:
event = self._event_offsets[response_type]
return event(buf)
@ensure_connected
def send_request(self, flags, xcb_parts, xcb_req):
return lib.xcb_send_request(self._conn, flags, xcb_parts, xcb_req)
@ensure_connected
def discard_reply(self, sequence):
return lib.xcb_discard_reply(self._conn, sequence)
# More backwards compatibility
connect = Connection
class Response(Protobj):
def __init__(self, unpacker):
Protobj.__init__(self, unpacker)
# These (and the ones in Reply) aren't used internally and I suspect
# they're not used by anyone else, but they're here for xpyb
# compatibility.
#
# In some cases (e.g. creating synthetic events from memory), we don't
# have the sequence number (since the event was fake), so only try to
# get these attributes if we are really using a cffi buffer.
if isinstance(unpacker, CffiUnpacker):
resp = unpacker.cast("xcb_generic_event_t *")
self.response_type = resp.response_type
self.sequence = resp.sequence
else:
self.response_type = None
self.sequence = None
class Reply(Response):
def __init__(self, unpacker):
Response.__init__(self, unpacker)
# also for compat
resp = unpacker.cast("xcb_generic_reply_t *")
self.length = resp.length
class Event(Response):
def __init__(self, unpacker):
# This is here for debugging purposes!
self.unpacker = unpacker
Response.__init__(self, unpacker)
# If this is a xcb_ge_generic_event_t (response type 35) then we need a few more fields
if self.xge and isinstance(unpacker, CffiUnpacker):
self.extension, self.length, self.event_type, self.full_sequence = unpacker.unpack("xB2xIH22xI")
# There's some extra work to do if the event has data past the 32 byte boundary
if self.length:
# Calculate the size of the original buffer. This is 4 bytes short as it seems to omit the `full_sequence` field
buffer_size = 32 + (self.length * 4) + 4
# Create a buffer object that's the full size
buffer = ffi.buffer(unpacker.cdata, buffer_size)
# Copy the event to the new buffer and skip the `full_sequence` field
buffer[32:buffer_size - 5] = buffer[36: buffer_size - 1]
# Provide the resized buffer to the unpacker
unpacker.buf = buffer
# The xge events in xcbproto do not include the fields in the first ten bytes but
# the generated code only skips four bytes so we need to adjust the starting offset
unpacker.offset = struct.calcsize("BBHIH") - struct.calcsize("xB2x")
class Error(Response, XcffibException):
def __init__(self, unpacker):
Response.__init__(self, unpacker)
XcffibException.__init__(self)
self.code = unpacker.unpack('B', increment=False)
def pack_list(from_, pack_type):
""" Return the wire packed version of `from_`. `pack_type` should be some
subclass of `xcffib.Struct`, or a string that can be passed to
`struct.pack`. You must pass `size` if `pack_type` is a struct.pack string.
"""
# We need from_ to not be empty
if len(from_) == 0:
return bytes()
if pack_type == 'c':
if isinstance(from_, bytes):
# Catch Python 3 bytes and Python 2 strings
# PY3 is "helpful" in that when you do tuple(b'foo') you get
# (102, 111, 111) instead of something more reasonable like
# (b'f', b'o', b'o'), so we rebuild from_ as a tuple of bytes
from_ = [bytes((b,)) for b in bytes(from_)]
elif isinstance(from_, str):
# Catch Python 3 strings and Python 2 unicode strings, both of
# which we encode to bytes as utf-8
# Here we create the tuple of bytes from the encoded string
from_ = [bytes((b,)) for b in bytearray(from_, 'utf-8')]
elif isinstance(from_[0], int):
# Pack from_ as char array, where from_ may be an array of ints
# possibly greater than 256
def to_bytes(v):
for _ in range(4):
v, r = divmod(v, 256)
yield r
from_ = [bytes((b,)) for i in from_ for b in to_bytes(i)]
if isinstance(pack_type, str):
return struct.pack("=%d%s" % (len(from_), pack_type), *from_)
else:
buf = io.BytesIO()
for item in from_:
# If we can't pack it, you'd better have packed it yourself. But
# let's not confuse things which aren't our Probobjs for packable
# things.
if isinstance(item, Protobj) and hasattr(item, "pack"):
buf.write(item.pack())
else:
buf.write(item)
return buf.getvalue()
def wrap(ptr):
c_conn = ffi.cast('xcb_connection_t *', ptr)
conn = Connection.__new__(Connection)
conn._conn = c_conn
conn._init_x()
conn.invalid()
# ptr owns the memory for c_conn, even after the cast
# we should keep it alive
cffi_explicit_lifetimes[conn] = ptr
return conn
def __DeviceTimeCoord_wrapper(typ, num_axes):
def init(unpacker):
i = typ(unpacker)
i.num_axes = num_axes
return i
xcffib-1.5.0/module/ffi.py 0000664 0000000 0000000 00000021366 14470210147 0015371 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
# Copyright 2014 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cffi import FFI
CONSTANTS = [
("X_PROTOCOL", 11),
("X_PROTOCOL_REVISION", 0),
("X_TCP_PORT", 6000),
("XCB_NONE", 0),
("XCB_COPY_FROM_PARENT", 0),
("XCB_CURRENT_TIME", 0),
("XCB_NO_SYMBOL", 0),
("XCB_CONN_ERROR", 1),
("XCB_CONN_CLOSED_EXT_NOTSUPPORTED", 2),
("XCB_CONN_CLOSED_MEM_INSUFFICIENT", 3),
("XCB_CONN_CLOSED_REQ_LEN_EXCEED", 4),
("XCB_CONN_CLOSED_PARSE_ERR", 5),
("XCB_CONN_CLOSED_INVALID_SCREEN", 6),
("XCB_CONN_CLOSED_FDPASSING_FAILED", 7),
("XCB_REQUEST_CHECKED", 1 << 0)
]
# constants
CDEF = '\n'.join("#define %s %d" % (c, v) for c, v in CONSTANTS)
# types
CDEF += """
// xcb.h
typedef struct {
uint8_t response_type; /**< Type of the response */
uint8_t pad0; /**< Padding */
uint16_t sequence; /**< Sequence number */
uint32_t length; /**< Length of the response */
} xcb_generic_reply_t;
typedef struct {
uint8_t response_type; /**< Type of the response */
uint8_t pad0; /**< Padding */
uint16_t sequence; /**< Sequence number */
uint32_t pad[7]; /**< Padding */
uint32_t full_sequence; /**< full sequence */
} xcb_generic_event_t;
typedef struct {
uint8_t response_type; /**< Type of the response */
uint8_t error_code; /**< Error code */
uint16_t sequence; /**< Sequence number */
uint32_t resource_id; /** < Resource ID for requests with side effects only */
uint16_t minor_code; /** < Minor opcode of the failed request */
uint8_t major_code; /** < Major opcode of the failed request */
uint8_t pad0;
uint32_t pad[5]; /**< Padding */
uint32_t full_sequence; /**< full sequence */
} xcb_generic_error_t;
typedef struct {
unsigned int sequence; /**< Sequence number */
} xcb_void_cookie_t;
typedef struct xcb_auth_info_t {
int namelen;
char *name;
int datalen;
char *data;
} xcb_auth_info_t;
typedef ... xcb_connection_t;
// xproto.h
typedef uint32_t xcb_colormap_t;
typedef uint32_t xcb_drawable_t;
typedef uint32_t xcb_pixmap_t;
typedef uint32_t xcb_visualid_t;
typedef uint32_t xcb_window_t;
typedef struct xcb_query_extension_reply_t {
uint8_t response_type;
uint8_t pad0;
uint16_t sequence;
uint32_t length;
uint8_t present;
uint8_t major_opcode;
uint8_t first_event;
uint8_t first_error;
} xcb_query_extension_reply_t;
typedef struct xcb_setup_t {
uint8_t status; /**< */
uint8_t pad0; /**< */
uint16_t protocol_major_version; /**< */
uint16_t protocol_minor_version; /**< */
uint16_t length; /**< */
uint32_t release_number; /**< */
uint32_t resource_id_base; /**< */
uint32_t resource_id_mask; /**< */
uint32_t motion_buffer_size; /**< */
uint16_t vendor_len; /**< */
uint16_t maximum_request_length; /**< */
uint8_t roots_len; /**< */
uint8_t pixmap_formats_len; /**< */
uint8_t image_byte_order; /**< */
uint8_t bitmap_format_bit_order; /**< */
uint8_t bitmap_format_scanline_unit; /**< */
uint8_t bitmap_format_scanline_pad; /**< */
uint8_t min_keycode; /**< */
uint8_t max_keycode; /**< */
uint8_t pad1[4]; /**< */
} xcb_setup_t;
typedef struct xcb_visualtype_t {
xcb_visualid_t visual_id; /**< */
uint8_t _class; /**< */
uint8_t bits_per_rgb_value; /**< */
uint16_t colormap_entries; /**< */
uint32_t red_mask; /**< */
uint32_t green_mask; /**< */
uint32_t blue_mask; /**< */
uint8_t pad0[4]; /**< */
} xcb_visualtype_t;
typedef struct xcb_screen_t {
xcb_window_t root; /**< */
xcb_colormap_t default_colormap; /**< */
uint32_t white_pixel; /**< */
uint32_t black_pixel; /**< */
uint32_t current_input_masks; /**< */
uint16_t width_in_pixels; /**< */
uint16_t height_in_pixels; /**< */
uint16_t width_in_millimeters; /**< */
uint16_t height_in_millimeters; /**< */
uint16_t min_installed_maps; /**< */
uint16_t max_installed_maps; /**< */
xcb_visualid_t root_visual; /**< */
uint8_t backing_stores; /**< */
uint8_t save_unders; /**< */
uint8_t root_depth; /**< */
uint8_t allowed_depths_len; /**< */
} xcb_screen_t;
typedef struct xcb_screen_iterator_t {
xcb_screen_t *data; /**< */
int rem; /**< */
int index; /**< */
} xcb_screen_iterator_t;
xcb_screen_iterator_t
xcb_setup_roots_iterator (const xcb_setup_t *R /**< */);
void
xcb_screen_next (xcb_screen_iterator_t *i /**< */);
// render.h
typedef uint32_t xcb_render_pictformat_t;
typedef struct xcb_render_directformat_t {
uint16_t red_shift; /**< */
uint16_t red_mask; /**< */
uint16_t green_shift; /**< */
uint16_t green_mask; /**< */
uint16_t blue_shift; /**< */
uint16_t blue_mask; /**< */
uint16_t alpha_shift; /**< */
uint16_t alpha_mask; /**< */
} xcb_render_directformat_t;
typedef struct xcb_render_pictforminfo_t {
xcb_render_pictformat_t id; /**< */
uint8_t type; /**< */
uint8_t depth; /**< */
uint8_t pad0[2]; /**< */
xcb_render_directformat_t direct; /**< */
xcb_colormap_t colormap; /**< */
} xcb_render_pictforminfo_t;
// xcbext.h
typedef struct xcb_extension_t {
const char *name;
int global_id;
} xcb_extension_t;
typedef struct {
size_t count;
xcb_extension_t *ext;
uint8_t opcode;
uint8_t isvoid;
} xcb_protocol_request_t;
// sys/uio.h
struct iovec
{
void *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
size_t iov_len; /* Must be size_t (1003.1g) */
};
// need to manually free some things that XCB allocates
void free(void *ptr);
"""
# connection manipulation, mostly generated with:
# grep -v '^[ \/\}#]' xcb.h | grep -v '^typedef' | grep -v '^extern'
CDEF += """
int xcb_flush(xcb_connection_t *c);
uint32_t xcb_get_maximum_request_length(xcb_connection_t *c);
void xcb_prefetch_maximum_request_length(xcb_connection_t *c);
xcb_generic_event_t *xcb_wait_for_event(xcb_connection_t *c);
xcb_generic_event_t *xcb_poll_for_event(xcb_connection_t *c);
const xcb_query_extension_reply_t *xcb_get_extension_data(xcb_connection_t *c, xcb_extension_t *ext);
const xcb_setup_t *xcb_get_setup(xcb_connection_t *c);
int xcb_get_file_descriptor(xcb_connection_t *c);
int xcb_connection_has_error(xcb_connection_t *c);
xcb_connection_t *xcb_connect_to_fd(int fd, xcb_auth_info_t *auth_info);
void xcb_disconnect(xcb_connection_t *c);
int xcb_parse_display(const char *name, char **host, int *display, int *screen);
xcb_connection_t *xcb_connect(const char *displayname, int *screenp);
xcb_connection_t *xcb_connect_to_display_with_auth_info(const char *display, xcb_auth_info_t *auth, int *screen);
uint32_t xcb_generate_id(xcb_connection_t *c);
xcb_generic_error_t *xcb_request_check(xcb_connection_t *c, xcb_void_cookie_t cookie);
"""
CDEF += """
unsigned int xcb_send_request(xcb_connection_t *c, int flags, struct iovec *vector, const xcb_protocol_request_t *request);
void *xcb_wait_for_reply(xcb_connection_t *c, unsigned int request, xcb_generic_error_t **e);
int xcb_poll_for_reply(xcb_connection_t *c, unsigned int request, void **reply, xcb_generic_error_t **error);
void xcb_discard_reply(xcb_connection_t *c, unsigned int sequence);
"""
ffi = FFI()
ffi.cdef(CDEF)
xcffib-1.5.0/module/testing.py 0000664 0000000 0000000 00000010410 14470210147 0016266 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Not strictly necessary to be included with the binding, but may be useful for
# others who want to test things using xcffib.
import errno
import fcntl
import os
import subprocess
import time
from . import Connection, ConnectionException
def lock_path(display):
return '/tmp/.X%d-lock' % display
def find_display():
display = 10
while True:
try:
f = open(lock_path(display), "w+")
try:
fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
f.close()
raise
except OSError:
display += 1
continue
return display, f
class XvfbTest:
""" A helper class for testing things with nosetests. This class will run
each test in its own fresh xvfb, leaving you with an xcffib connection to
that X session as `self.conn` for use in testing. """
# Set this to true if you'd like to get xtrace output to stdout of each
# test.
xtrace = False
def __init__(self, width=800, height=600, depth=16):
self.width = width
self.height = height
self.depth = depth
def spawn(self, cmd):
""" Spawn a command but swallow its output. """
return subprocess.Popen(cmd)
def _restore_display(self):
if self._old_display is None:
del os.environ['DISPLAY']
else:
os.environ['DISPLAY'] = self._old_display
def setUp(self):
self._old_display = os.environ.get('DISPLAY')
self._display, self._display_lock = find_display()
os.environ['DISPLAY'] = ':%d' % self._display
self._xvfb = self.spawn(self._xvfb_command())
if self.xtrace:
subprocess.Popen(['xtrace', '-n'])
# xtrace's default display is :9; obviously this won't work
# concurrently, but it's not the default so...
os.environ['DISPLAY'] = ':9'
try:
self.conn = self._connect_to_xvfb()
except AssertionError:
self._restore_display()
raise
def tearDown(self):
try:
self.conn.disconnect()
except ConnectionException:
# We don't care if the connection was in an invalid state, maybe
# the test failed.
pass
finally:
self.conn = None
self._xvfb.kill()
self._xvfb.wait()
self._xvfb = None
# Delete our X lock file too, since we .kill() the process so it won't
# clean up after itself.
try:
self._display_lock.close()
os.remove(lock_path(self._display))
except OSError as e:
# we don't care if it doesn't exist, maybe something crashed and
# cleaned it up during a test.
if e.errno != errno.ENOENT:
raise
finally:
self._restore_display()
def __enter__(self):
self.setUp()
return self
def __exit__(self, type, value, traceback):
self.tearDown()
def _xvfb_command(self):
""" You can override this if you have some extra args for Xvfb or
whatever. At this point, os.environ['DISPLAY'] is set to something Xvfb
can use. """
screen = '%sx%sx%s' % (self.width, self.height, self.depth)
return ['Xvfb', os.environ['DISPLAY'], '-screen', '0', screen]
def _connect_to_xvfb(self):
# sometimes it takes a while for Xvfb to start
for _ in range(100):
try:
conn = Connection(os.environ['DISPLAY'])
conn.invalid()
return conn
except ConnectionException:
time.sleep(0.2)
assert False, "couldn't connect to xvfb"
xcffib-1.5.0/module/wrappers.py 0000664 0000000 0000000 00000002231 14470210147 0016456 0 ustar 00root root 0000000 0000000 # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
def IDWrapper(freer):
"""
Classes create with IDWrapper return an ID and then free it upon exit of the
context.
"""
class Wrapper:
def __init__(self, conn):
self.conn = conn
self.id = None
def __enter__(self):
self.id = self.conn.generate_id()
return self.id
def __exit__(self, exception_type, exception_value, traceback):
getattr(self.conn.core, freer)(self.id)
return Wrapper
PixmapID = IDWrapper("FreePixmap")
GContextID = IDWrapper("FreeGC")
ColormapID = IDWrapper("FreeColormap")
CursorID = IDWrapper("FreeCursor")
xcffib-1.5.0/requirements.txt 0000664 0000000 0000000 00000000034 14470210147 0016237 0 ustar 00root root 0000000 0000000 flake8
autopep8
cffi>=0.8.2
xcffib-1.5.0/setup.py 0000664 0000000 0000000 00000005641 14470210147 0014476 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
#
# Copyright 2014 Tycho Andersen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from setuptools import setup
from setuptools.command.install import install
from distutils.command.build import build
class binding_build(build):
"""This is a check to ensure that the bindings have been generated, and
print a helpful message if they have not been generated yet. We only need
to check this when we are actually building or installing.
"""
def finalize_options(self):
if not os.path.exists('./xcffib'):
print("It looks like you need to generate the binding.")
print("please run 'make xcffib' or 'make check'.")
sys.exit(1)
build.finalize_options(self)
class binding_install(install):
def finalize_options(self):
if not os.path.exists('./xcffib'):
print("It looks like you need to generate the binding.")
print("please run 'make xcffib' or 'make check'.")
sys.exit(1)
install.finalize_options(self)
# Check if we're running PyPy, cffi can't be updated
if '_cffi_backend' in sys.builtin_module_names:
import _cffi_backend
requires_cffi = "cffi==" + _cffi_backend.__version__
else:
requires_cffi = "cffi>=1.1.0"
version = "1.5.0"
dependencies = [requires_cffi]
setup(
name="xcffib",
version=version,
description="A drop in replacement for xpyb, an XCB python binding",
keywords="xcb xpyb cffi x11 x windows",
license="Apache License 2.0",
url="http://github.com/tych0/xcffib",
author="Tycho Andersen",
author_email="tycho@tycho.pizza",
install_requires=dependencies,
setup_requires=dependencies,
packages=['xcffib'],
package_data={'xcffib': ['py.typed']},
zip_safe=False,
cmdclass={
'build': binding_build,
'install': binding_install
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries'
],
)
xcffib-1.5.0/test/ 0000775 0000000 0000000 00000000000 14470210147 0013735 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/test/GeneratorTests.hs 0000664 0000000 0000000 00000004137 14470210147 0017247 0 ustar 00root root 0000000 0000000 {-
- Copyright 2014 Tycho Andersen
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
module Main (main) where
import Language.Python.Common
import Data.XCB.Python.Parse
import Data.XCB.FromXML
import Data.XCB.Types
import Test.Framework ( defaultMain, Test )
import Test.Framework.Providers.HUnit
import Test.HUnit hiding ( Test )
import System.FilePath
pyTests :: [String]
pyTests = [ "event"
, "error"
, "request"
, "union"
, "struct"
, "enum"
, "request_reply"
, "no_sequence"
, "type_pad"
, "switch"
, "render_1.7"
, "xproto_1.7"
, "render"
, "randr"
, "eventstruct"
]
mkFname :: String -> FilePath
mkFname = (>) $ "test" > "generator"
mkTest :: String -> IO Test
mkTest name = do
header <- fromFiles [mkFname $ name <.> ".xml"]
rawExpected <- readFile . mkFname $ name <.> ".py"
let [(fname, outPy)] = xform header
rawOut = renderPy outPy
return $ testCase name $ do assertEqual "names equal" name fname
-- TODO: we should really parse and compare ASTs
assertEqual "rendering equal" rawExpected rawOut
calcsizeTests :: [Test]
calcsizeTests =
let tests = [ ("x2xBx", 5)
, ("24xHhII", 24 + 2 * 2 + 2 * 4)
]
in map mkTest tests
where
mkTest (str, expected) =
let result = calcsize str
in testCase "calcsize" (assertEqual str expected result)
main :: IO ()
main = do
genTests <- mapM mkTest pyTests
defaultMain $ calcsizeTests ++ genTests
xcffib-1.5.0/test/PyHelpersTests.hs 0000664 0000000 0000000 00000002560 14470210147 0017232 0 ustar 00root root 0000000 0000000 {-
- Copyright 2014 Tycho Andersen
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-}
module Main (main) where
import Language.Python.Common
import Data.XCB.Python.PyHelpers
import Test.Framework ( defaultMain, Test )
import Test.Framework.Providers.HUnit
import Test.HUnit hiding ( Test )
mkTest :: (Show a, Eq a) => String -> a -> a -> Test
mkTest name t1 t2 = testCase name (assertEqual name t1 t2)
testMkName :: Test
testMkName =
let result = mkName "self.foo.bar"
expected = (Dot (Dot (Var (Ident "self" ()) ())
(Ident "foo" ()) ())
(Ident "bar" ()) ())
in mkTest "testMkName" expected result
testReserves :: Test
testReserves =
let result = mkName "None"
expected = (Var (Ident "_None" ()) ())
in mkTest "testReserves" expected result
main :: IO ()
main = do
defaultMain [testMkName, testReserves]
xcffib-1.5.0/test/__init__.py 0000664 0000000 0000000 00000000000 14470210147 0016034 0 ustar 00root root 0000000 0000000 xcffib-1.5.0/test/conftest.py 0000664 0000000 0000000 00000004717 14470210147 0016145 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
# Copyright 2021 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import xcffib
from xcffib.testing import XvfbTest
from xcffib.xproto import EventMask
import pytest
@pytest.fixture
def xcffib_test():
with XcffibTest() as test_data:
yield test_data
@pytest.fixture
def xproto_test(xcffib_test):
xcffib_test.xproto = xcffib.xproto.xprotoExtension(xcffib_test.conn)
return xcffib_test
class XcffibTest(XvfbTest):
""" A home for common functions needed for xcffib testing. """
def setUp(self):
XvfbTest.setUp(self)
self.xproto = xcffib.xproto.xprotoExtension(self.conn)
def tearDown(self):
self.xproto = None
XvfbTest.tearDown(self)
@property
def default_screen(self):
return self.conn.setup.roots[self.conn.pref_screen]
def create_window(self, wid=None, x=0, y=0, w=1, h=1, is_checked=False):
if wid is None:
wid = self.conn.generate_id()
return self.xproto.CreateWindow(
self.default_screen.root_depth,
wid,
self.default_screen.root,
x, y, w, h,
0,
xcffib.xproto.WindowClass.InputOutput,
self.default_screen.root_visual,
xcffib.xproto.CW.BackPixel | xcffib.xproto.CW.EventMask,
[
self.default_screen.black_pixel,
xcffib.xproto.EventMask.StructureNotify
],
is_checked=is_checked
)
def xeyes(self):
# Enable CreateNotify
self.xproto.ChangeWindowAttributesChecked(
self.default_screen.root,
xcffib.xproto.CW.EventMask,
[
EventMask.SubstructureNotify |
EventMask.StructureNotify |
EventMask.SubstructureRedirect
]
).check()
self.spawn(['xeyes'])
def intern(self, name):
return self.xproto.InternAtom(False, len(name), name).reply().atom
xcffib-1.5.0/test/flake8.cfg 0000664 0000000 0000000 00000000460 14470210147 0015570 0 ustar 00root root 0000000 0000000 # F401: unused imports. xcb actually does some internal state changing on
# imports, so in fact these /are/ used, just not directly.
#
# E501: line too long. This is from the copy and pasted function definitons
# from cairo headers, which I think are best left as one liners.
[flake8]
ignore = F401,E501
xcffib-1.5.0/test/generator/ 0000775 0000000 0000000 00000000000 14470210147 0015723 5 ustar 00root root 0000000 0000000 xcffib-1.5.0/test/generator/enum.py 0000664 0000000 0000000 00000001727 14470210147 0017250 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class DeviceUse:
IsXPointer = 0
IsXKeyboard = 1
IsXExtensionDevice = 2
IsXExtensionKeyboard = 3
IsXExtensionPointer = 4
class EventMask:
NoEvent = 0
KeyPress = 1 << 0
KeyRelease = 1 << 1
ButtonPress = 1 << 2
ButtonRelease = 1 << 3
EnterWindow = 1 << 4
LeaveWindow = 1 << 5
PointerMotion = 1 << 6
PointerMotionHint = 1 << 7
Button1Motion = 1 << 8
Button2Motion = 1 << 9
Button3Motion = 1 << 10
Button4Motion = 1 << 11
Button5Motion = 1 << 12
ButtonMotion = 1 << 13
KeymapState = 1 << 14
Exposure = 1 << 15
VisibilityChange = 1 << 16
StructureNotify = 1 << 17
ResizeRedirect = 1 << 18
SubstructureNotify = 1 << 19
SubstructureRedirect = 1 << 20
FocusChange = 1 << 21
PropertyChange = 1 << 22
ColorMapChange = 1 << 23
OwnerGrabButton = 1 << 24
xcffib._add_ext(key, enumExtension, _events, _errors)
xcffib-1.5.0/test/generator/enum.xml 0000664 0000000 0000000 00000003714 14470210147 0017416 0 ustar 00root root 0000000 0000000
- 0
- 1
- 2
- 3
- 4
- 0
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
xcffib-1.5.0/test/generator/error.py 0000664 0000000 0000000 00000001541 14470210147 0017427 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 2
MINOR_VERSION = 2
key = xcffib.ExtensionKey("ERROR")
_events = {}
_errors = {}
class RequestError(xcffib.Error):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Error.__init__(self, unpacker)
base = unpacker.offset
self.bad_value, self.minor_opcode, self.major_opcode = unpacker.unpack("xx2xIHBx")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=B", 1))
buf.write(struct.pack("=x2xIHBx", self.bad_value, self.minor_opcode, self.major_opcode))
return buf.getvalue()
BadRequest = RequestError
_errors[1] = RequestError
xcffib._add_ext(key, errorExtension, _events, _errors)
xcffib-1.5.0/test/generator/error.xml 0000664 0000000 0000000 00000000535 14470210147 0017601 0 ustar 00root root 0000000 0000000
xcffib-1.5.0/test/generator/event.py 0000664 0000000 0000000 00000003430 14470210147 0017416 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 1
MINOR_VERSION = 4
key = xcffib.ExtensionKey("EVENT")
_events = {}
_errors = {}
class ScreenChangeNotifyEvent(xcffib.Event):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Event.__init__(self, unpacker)
base = unpacker.offset
self.rotation, self.timestamp, self.config_timestamp, self.root, self.request_window, self.sizeID, self.subpixel_order, self.width, self.height, self.mwidth, self.mheight = unpacker.unpack("xB2xIIIIHHHHHH")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=B", 0))
buf.write(struct.pack("=B2xIIIIHHHHHH", self.rotation, self.timestamp, self.config_timestamp, self.root, self.request_window, self.sizeID, self.subpixel_order, self.width, self.height, self.mwidth, self.mheight))
buf_len = len(buf.getvalue())
if buf_len < 32:
buf.write(struct.pack("x" * (32 - buf_len)))
return buf.getvalue()
@classmethod
def synthetic(cls, rotation, timestamp, config_timestamp, root, request_window, sizeID, subpixel_order, width, height, mwidth, mheight):
self = cls.__new__(cls)
self.rotation = rotation
self.timestamp = timestamp
self.config_timestamp = config_timestamp
self.root = root
self.request_window = request_window
self.sizeID = sizeID
self.subpixel_order = subpixel_order
self.width = width
self.height = height
self.mwidth = mwidth
self.mheight = mheight
return self
_events[0] = ScreenChangeNotifyEvent
xcffib._add_ext(key, eventExtension, _events, _errors)
xcffib-1.5.0/test/generator/event.xml 0000664 0000000 0000000 00000001451 14470210147 0017567 0 ustar 00root root 0000000 0000000
xcffib-1.5.0/test/generator/eventstruct.py 0000664 0000000 0000000 00000001160 14470210147 0020661 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class EventForSend(xcffib.Buffer):
pass
class eventstructExtension(xcffib.Extension):
def SendExtensionEvent(self, device_id, propagate, num_classes, num_events, events, classes, is_checked=False):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xBBHB3x", device_id, propagate, num_classes, num_events))
buf.write(xcffib.pack_list(events, EventForSend))
buf.write(xcffib.pack_list(classes, "B"))
return self.send_request(31, buf, is_checked=is_checked)
xcffib._add_ext(key, eventstructExtension, _events, _errors)
xcffib-1.5.0/test/generator/eventstruct.xml 0000664 0000000 0000000 00000002651 14470210147 0021037 0 ustar 00root root 0000000 0000000
num_events
num_classes
xcffib-1.5.0/test/generator/no_sequence.py 0000664 0000000 0000000 00000001716 14470210147 0020606 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class KeymapNotifyEvent(xcffib.Event):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Event.__init__(self, unpacker)
base = unpacker.offset
unpacker.unpack("x")
self.keys = xcffib.List(unpacker, "B", 31)
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=B", 11))
buf.write(xcffib.pack_list(self.keys, "B"))
buf_len = len(buf.getvalue())
if buf_len < 32:
buf.write(struct.pack("x" * (32 - buf_len)))
return buf.getvalue()
@classmethod
def synthetic(cls, keys):
self = cls.__new__(cls)
self.keys = keys
return self
_events[11] = KeymapNotifyEvent
xcffib._add_ext(key, no_sequenceExtension, _events, _errors)
xcffib-1.5.0/test/generator/no_sequence.xml 0000664 0000000 0000000 00000000255 14470210147 0020753 0 ustar 00root root 0000000 0000000
31
xcffib-1.5.0/test/generator/randr.py 0000664 0000000 0000000 00000005465 14470210147 0017415 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 1
MINOR_VERSION = 6
key = xcffib.ExtensionKey("RANDR")
_events = {}
_errors = {}
class TRANSFORM(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.matrix11, self.matrix12, self.matrix13, self.matrix21, self.matrix22, self.matrix23, self.matrix31, self.matrix32, self.matrix33 = unpacker.unpack("iiiiiiiii")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=iiiiiiiii", self.matrix11, self.matrix12, self.matrix13, self.matrix21, self.matrix22, self.matrix23, self.matrix31, self.matrix32, self.matrix33))
return buf.getvalue()
fixed_size = 36
@classmethod
def synthetic(cls, matrix11, matrix12, matrix13, matrix21, matrix22, matrix23, matrix31, matrix32, matrix33):
self = cls.__new__(cls)
self.matrix11 = matrix11
self.matrix12 = matrix12
self.matrix13 = matrix13
self.matrix21 = matrix21
self.matrix22 = matrix22
self.matrix23 = matrix23
self.matrix31 = matrix31
self.matrix32 = matrix32
self.matrix33 = matrix33
return self
class GetCrtcTransformReply(xcffib.Reply):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Reply.__init__(self, unpacker)
base = unpacker.offset
unpacker.unpack("xx2x4x")
self.pending_transform = TRANSFORM(unpacker)
self.has_transforms, = unpacker.unpack("B3x")
unpacker.pad(TRANSFORM)
self.current_transform = TRANSFORM(unpacker)
self.pending_len, self.pending_nparams, self.current_len, self.current_nparams = unpacker.unpack("4xHHHH")
unpacker.pad("c")
self.pending_filter_name = xcffib.List(unpacker, "c", self.pending_len)
unpacker.pad("i")
self.pending_params = xcffib.List(unpacker, "i", self.pending_nparams)
unpacker.pad("c")
self.current_filter_name = xcffib.List(unpacker, "c", self.current_len)
unpacker.pad("i")
self.current_params = xcffib.List(unpacker, "i", self.current_nparams)
self.bufsize = unpacker.offset - base
class GetCrtcTransformCookie(xcffib.Cookie):
reply_type = GetCrtcTransformReply
class randrExtension(xcffib.Extension):
def GetCrtcTransform(self, crtc, is_checked=True):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xI", crtc))
return self.send_request(27, buf, GetCrtcTransformCookie, is_checked=is_checked)
xcffib._add_ext(key, randrExtension, _events, _errors)
xcffib-1.5.0/test/generator/randr.xml 0000664 0000000 0000000 00000003226 14470210147 0017556 0 ustar 00root root 0000000 0000000
pending_len
pending_nparams
current_len
current_nparams
xcffib-1.5.0/test/generator/render.py 0000664 0000000 0000000 00000004201 14470210147 0017551 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 0
MINOR_VERSION = 11
key = xcffib.ExtensionKey("RENDER")
_events = {}
_errors = {}
class COLOR(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.red, self.green, self.blue, self.alpha = unpacker.unpack("HHHH")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=HHHH", self.red, self.green, self.blue, self.alpha))
return buf.getvalue()
fixed_size = 8
@classmethod
def synthetic(cls, red, green, blue, alpha):
self = cls.__new__(cls)
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
return self
class RECTANGLE(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.x, self.y, self.width, self.height = unpacker.unpack("hhHH")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=hhHH", self.x, self.y, self.width, self.height))
return buf.getvalue()
fixed_size = 8
@classmethod
def synthetic(cls, x, y, width, height):
self = cls.__new__(cls)
self.x = x
self.y = y
self.width = width
self.height = height
return self
class renderExtension(xcffib.Extension):
def FillRectangles(self, op, dst, color, rects_len, rects, is_checked=False):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xB3xI", op, dst))
buf.write(color.pack() if hasattr(color, "pack") else COLOR.synthetic(*color).pack())
buf.write(xcffib.pack_list(rects, RECTANGLE))
return self.send_request(26, buf, is_checked=is_checked)
xcffib._add_ext(key, renderExtension, _events, _errors)
xcffib-1.5.0/test/generator/render.xml 0000664 0000000 0000000 00000001437 14470210147 0017731 0 ustar 00root root 0000000 0000000
xcffib-1.5.0/test/generator/render_1.7.py 0000664 0000000 0000000 00000002365 14470210147 0020147 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 0
MINOR_VERSION = 11
key = xcffib.ExtensionKey("RENDER")
_events = {}
_errors = {}
class PictOp:
Clear = 0
Src = 1
Dst = 2
Over = 3
OverReverse = 4
In = 5
InReverse = 6
Out = 7
OutReverse = 8
Atop = 9
AtopReverse = 10
Xor = 11
Add = 12
Saturate = 13
DisjointClear = 16
DisjointSrc = 17
DisjointDst = 18
DisjointOver = 19
DisjointOverReverse = 20
DisjointIn = 21
DisjointInReverse = 22
DisjointOut = 23
DisjointOutReverse = 24
DisjointAtop = 25
DisjointAtopReverse = 26
DisjointXor = 27
ConjointClear = 32
ConjointSrc = 33
ConjointDst = 34
ConjointOver = 35
ConjointOverReverse = 36
ConjointIn = 37
ConjointInReverse = 38
ConjointOut = 39
ConjointOutReverse = 40
ConjointAtop = 41
ConjointAtopReverse = 42
ConjointXor = 43
Multiply = 48
Screen = 49
Overlay = 50
Darken = 51
Lighten = 52
ColorDodge = 53
ColorBurn = 54
HardLight = 55
SoftLight = 56
Difference = 57
Exclusion = 58
HSLHue = 59
HSLSaturation = 60
HSLColor = 61
HSLLuminosity = 62
xcffib._add_ext(key, render_1._7Extension, _events, _errors)
xcffib-1.5.0/test/generator/render_1.7.xml 0000664 0000000 0000000 00000004342 14470210147 0020314 0 ustar 00root root 0000000 0000000
- 16
- 32
- 48
xcffib-1.5.0/test/generator/request.py 0000664 0000000 0000000 00000001152 14470210147 0017764 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class requestExtension(xcffib.Extension):
def CreateWindow(self, depth, wid, parent, x, y, width, height, border_width, _class, visual, value_mask, value_list, is_checked=False):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xBIIhhHHHHI", depth, wid, parent, x, y, width, height, border_width, _class, visual))
buf.write(struct.pack("=I", value_mask))
buf.write(xcffib.pack_list(value_list, "I"))
return self.send_request(1, buf, is_checked=is_checked)
xcffib._add_ext(key, requestExtension, _events, _errors)
xcffib-1.5.0/test/generator/request.xml 0000664 0000000 0000000 00000006324 14470210147 0020142 0 ustar 00root root 0000000 0000000
Creates a window
xcffib-1.5.0/test/generator/request_reply.py 0000664 0000000 0000000 00000003203 14470210147 0021176 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class STR(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.name_len, = unpacker.unpack("B")
self.name = xcffib.List(unpacker, "c", self.name_len)
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=B", self.name_len))
buf.write(xcffib.pack_list(self.name, "c"))
return buf.getvalue()
@classmethod
def synthetic(cls, name_len, name):
self = cls.__new__(cls)
self.name_len = name_len
self.name = name
return self
class ListExtensionsReply(xcffib.Reply):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Reply.__init__(self, unpacker)
base = unpacker.offset
self.names_len, = unpacker.unpack("xB2x4x24x")
self.names = xcffib.List(unpacker, STR, self.names_len)
self.bufsize = unpacker.offset - base
class ListExtensionsCookie(xcffib.Cookie):
reply_type = ListExtensionsReply
class request_replyExtension(xcffib.Extension):
def ListExtensions(self, is_checked=True):
buf = io.BytesIO()
buf.write(struct.pack("=xx2x"))
return self.send_request(99, buf, ListExtensionsCookie, is_checked=is_checked)
xcffib._add_ext(key, request_replyExtension, _events, _errors)
xcffib-1.5.0/test/generator/request_reply.xml 0000664 0000000 0000000 00000000725 14470210147 0021354 0 ustar 00root root 0000000 0000000
name_len
names_len
xcffib-1.5.0/test/generator/struct.py 0000664 0000000 0000000 00000003663 14470210147 0017631 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class AxisInfo(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.resolution, self.minimum, self.maximum = unpacker.unpack("Iii")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=Iii", self.resolution, self.minimum, self.maximum))
return buf.getvalue()
fixed_size = 12
@classmethod
def synthetic(cls, resolution, minimum, maximum):
self = cls.__new__(cls)
self.resolution = resolution
self.minimum = minimum
self.maximum = maximum
return self
class ValuatorInfo(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.class_id, self.len, self.axes_len, self.mode, self.motion_size = unpacker.unpack("BBBBI")
self.axes = xcffib.List(unpacker, AxisInfo, self.axes_len)
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=BBBBI", self.class_id, self.len, self.axes_len, self.mode, self.motion_size))
buf.write(xcffib.pack_list(self.axes, AxisInfo))
return buf.getvalue()
@classmethod
def synthetic(cls, class_id, len, axes_len, mode, motion_size, axes):
self = cls.__new__(cls)
self.class_id = class_id
self.len = len
self.axes_len = axes_len
self.mode = mode
self.motion_size = motion_size
self.axes = axes
return self
xcffib._add_ext(key, structExtension, _events, _errors)
xcffib-1.5.0/test/generator/struct.xml 0000664 0000000 0000000 00000001235 14470210147 0017772 0 ustar 00root root 0000000 0000000
axes_len
xcffib-1.5.0/test/generator/switch.py 0000664 0000000 0000000 00000007004 14470210147 0017577 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class INT64(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.hi, self.lo = unpacker.unpack("iI")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=iI", self.hi, self.lo))
return buf.getvalue()
fixed_size = 8
@classmethod
def synthetic(cls, hi, lo):
self = cls.__new__(cls)
self.hi = hi
self.lo = lo
return self
class GetPropertyReply(xcffib.Reply):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Reply.__init__(self, unpacker)
base = unpacker.offset
self.num_items, self.format = unpacker.unpack("xx2x4xIB")
if self.format & PropertyFormat._8Bits:
self.data8 = xcffib.List(unpacker, "B", self.num_items)
if self.format & PropertyFormat._16Bits:
self.data16 = xcffib.List(unpacker, "H", self.num_items)
if self.format & PropertyFormat._32Bits:
self.data32 = xcffib.List(unpacker, "I", self.num_items)
self.bufsize = unpacker.offset - base
class GetPropertyCookie(xcffib.Cookie):
reply_type = GetPropertyReply
class GetPropertyWithPadReply(xcffib.Reply):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Reply.__init__(self, unpacker)
base = unpacker.offset
self.num_items, self.format = unpacker.unpack("xx2x4xIB")
self.names = xcffib.List(unpacker, "B", self.num_items)
if self.format & PropertyFormat._8Bits:
unpacker.pad("B")
self.data8 = xcffib.List(unpacker, "B", self.num_items)
if self.format & PropertyFormat._16Bits:
unpacker.pad("H")
self.data16 = xcffib.List(unpacker, "H", self.num_items)
if self.format & PropertyFormat._32Bits:
unpacker.pad("I")
self.data32 = xcffib.List(unpacker, "I", self.num_items)
self.bufsize = unpacker.offset - base
class GetPropertyWithPadCookie(xcffib.Cookie):
reply_type = GetPropertyWithPadReply
class switchExtension(xcffib.Extension):
def GetProperty(self, value_mask, items, is_checked=True):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xI", value_mask))
if value_mask & CA.Counter:
counter = items.pop(0)
buf.write(struct.pack("=I", counter))
if value_mask & CA.Value:
value = items.pop(0)
buf.write(value.pack() if hasattr(value, "pack") else INT64.synthetic(*value).pack())
if value_mask & CA.ValueType:
valueType = items.pop(0)
buf.write(struct.pack("=I", valueType))
if value_mask & CA.Events:
events = items.pop(0)
buf.write(struct.pack("=I", events))
return self.send_request(59, buf, GetPropertyCookie, is_checked=is_checked)
def GetPropertyWithPad(self, is_checked=True):
buf = io.BytesIO()
buf.write(struct.pack("=xx2x"))
return self.send_request(60, buf, GetPropertyWithPadCookie, is_checked=is_checked)
xcffib._add_ext(key, switchExtension, _events, _errors)
xcffib-1.5.0/test/generator/switch.xml 0000664 0000000 0000000 00000005520 14470210147 0017750 0 ustar 00root root 0000000 0000000
value_mask
Counter
Value
ValueType
Events
format
8Bits
num_items
16Bits
num_items
32Bits
num_items
num_items
format
8Bits
num_items
16Bits
num_items
32Bits
num_items
xcffib-1.5.0/test/generator/type_pad.py 0000664 0000000 0000000 00000006474 14470210147 0020115 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class CHARINFO(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.left_side_bearing, self.right_side_bearing, self.character_width, self.ascent, self.descent, self.attributes = unpacker.unpack("hhhhhH")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=hhhhhH", self.left_side_bearing, self.right_side_bearing, self.character_width, self.ascent, self.descent, self.attributes))
return buf.getvalue()
fixed_size = 12
@classmethod
def synthetic(cls, left_side_bearing, right_side_bearing, character_width, ascent, descent, attributes):
self = cls.__new__(cls)
self.left_side_bearing = left_side_bearing
self.right_side_bearing = right_side_bearing
self.character_width = character_width
self.ascent = ascent
self.descent = descent
self.attributes = attributes
return self
class FONTPROP(xcffib.Struct):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Struct.__init__(self, unpacker)
base = unpacker.offset
self.name, self.value = unpacker.unpack("II")
self.bufsize = unpacker.offset - base
def pack(self):
buf = io.BytesIO()
buf.write(struct.pack("=II", self.name, self.value))
return buf.getvalue()
fixed_size = 8
@classmethod
def synthetic(cls, name, value):
self = cls.__new__(cls)
self.name = name
self.value = value
return self
class ListFontsWithInfoReply(xcffib.Reply):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Reply.__init__(self, unpacker)
base = unpacker.offset
self.name_len, = unpacker.unpack("xB2x4x")
self.min_bounds = CHARINFO(unpacker)
unpacker.unpack("4x")
unpacker.pad(CHARINFO)
self.max_bounds = CHARINFO(unpacker)
self.min_char_or_byte2, self.max_char_or_byte2, self.default_char, self.properties_len, self.draw_direction, self.min_byte1, self.max_byte1, self.all_chars_exist, self.font_ascent, self.font_descent, self.replies_hint = unpacker.unpack("4xHHHHBBBBhhI")
unpacker.pad(FONTPROP)
self.properties = xcffib.List(unpacker, FONTPROP, self.properties_len)
unpacker.pad("c")
self.name = xcffib.List(unpacker, "c", self.name_len)
self.bufsize = unpacker.offset - base
class ListFontsWithInfoCookie(xcffib.Cookie):
reply_type = ListFontsWithInfoReply
class type_padExtension(xcffib.Extension):
def ListFontsWithInfo(self, max_names, pattern_len, pattern, is_checked=True):
buf = io.BytesIO()
buf.write(struct.pack("=xx2xxHH", max_names, pattern_len))
buf.write(xcffib.pack_list(pattern, "c"))
return self.send_request(50, buf, ListFontsWithInfoCookie, is_checked=is_checked)
xcffib._add_ext(key, type_padExtension, _events, _errors)
xcffib-1.5.0/test/generator/type_pad.xml 0000664 0000000 0000000 00000007303 14470210147 0020255 0 ustar 00root root 0000000 0000000
pattern_len
properties_len
name_len
get matching font names and information
xcffib-1.5.0/test/generator/union.py 0000664 0000000 0000000 00000001241 14470210147 0017423 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
_events = {}
_errors = {}
class ClientMessageData(xcffib.Union):
xge = False
def __init__(self, unpacker):
if isinstance(unpacker, xcffib.Protobj):
unpacker = xcffib.MemoryUnpacker(unpacker.pack())
xcffib.Union.__init__(self, unpacker)
self.data8 = xcffib.List(unpacker.copy(), "B", 20)
self.data16 = xcffib.List(unpacker.copy(), "H", 10)
self.data32 = xcffib.List(unpacker.copy(), "I", 5)
def pack(self):
buf = io.BytesIO()
buf.write(xcffib.pack_list(self.data8, "B"))
return buf.getvalue()
xcffib._add_ext(key, unionExtension, _events, _errors)
xcffib-1.5.0/test/generator/union.xml 0000664 0000000 0000000 00000000650 14470210147 0017576 0 ustar 00root root 0000000 0000000
20
10
5
xcffib-1.5.0/test/generator/xinput.xml 0000664 0000000 0000000 00000005466 14470210147 0020007 0 ustar 00root root 0000000 0000000
- 0
- 1
- 2
- 3
- 8
- 9
len
4
type
Key
num_keys
Button
num_buttons
31
32
num_buttons
Valuator
Scroll
Touch
Gesture
xcffib-1.5.0/test/generator/xproto_1.7.py 0000664 0000000 0000000 00000003105 14470210147 0020214 0 ustar 00root root 0000000 0000000 import xcffib
import struct
import io
MAJOR_VERSION = 0
MINOR_VERSION = 11
key = xcffib.ExtensionKey("XPROTO")
_events = {}
_errors = {}
class Atom:
_None = 0
Any = 0
PRIMARY = 1
SECONDARY = 2
ARC = 3
ATOM = 4
BITMAP = 5
CARDINAL = 6
COLORMAP = 7
CURSOR = 8
CUT_BUFFER0 = 9
CUT_BUFFER1 = 10
CUT_BUFFER2 = 11
CUT_BUFFER3 = 12
CUT_BUFFER4 = 13
CUT_BUFFER5 = 14
CUT_BUFFER6 = 15
CUT_BUFFER7 = 16
DRAWABLE = 17
FONT = 18
INTEGER = 19
PIXMAP = 20
POINT = 21
RECTANGLE = 22
RESOURCE_MANAGER = 23
RGB_COLOR_MAP = 24
RGB_BEST_MAP = 25
RGB_BLUE_MAP = 26
RGB_DEFAULT_MAP = 27
RGB_GRAY_MAP = 28
RGB_GREEN_MAP = 29
RGB_RED_MAP = 30
STRING = 31
VISUALID = 32
WINDOW = 33
WM_COMMAND = 34
WM_HINTS = 35
WM_CLIENT_MACHINE = 36
WM_ICON_NAME = 37
WM_ICON_SIZE = 38
WM_NAME = 39
WM_NORMAL_HINTS = 40
WM_SIZE_HINTS = 41
WM_ZOOM_HINTS = 42
MIN_SPACE = 43
NORM_SPACE = 44
MAX_SPACE = 45
END_SPACE = 46
SUPERSCRIPT_X = 47
SUPERSCRIPT_Y = 48
SUBSCRIPT_X = 49
SUBSCRIPT_Y = 50
UNDERLINE_POSITION = 51
UNDERLINE_THICKNESS = 52
STRIKEOUT_ASCENT = 53
STRIKEOUT_DESCENT = 54
ITALIC_ANGLE = 55
X_HEIGHT = 56
QUAD_WIDTH = 57
WEIGHT = 58
POINT_SIZE = 59
RESOLUTION = 60
COPYRIGHT = 61
NOTICE = 62
FONT_NAME = 63
FAMILY_NAME = 64
FULL_NAME = 65
CAP_HEIGHT = 66
WM_CLASS = 67
WM_TRANSIENT_FOR = 68
xcffib._add_ext(key, xproto_1._7Extension, _events, _errors)
xcffib-1.5.0/test/generator/xproto_1.7.xml 0000664 0000000 0000000 00000005054 14470210147 0020371 0 ustar 00root root 0000000 0000000
- 0
- 0
xcffib-1.5.0/test/test_connection.py 0000664 0000000 0000000 00000026543 14470210147 0017517 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
# Copyright 2014 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import xcffib
import xcffib.xproto
from xcffib import ffi
import pytest
class TestConnection:
def test_invalid_display(self, xproto_test):
with pytest.raises(xcffib.ConnectionException):
xproto_test.conn = xcffib.Connection("notvalid")
def test_get_setup(self, xproto_test):
setup = xproto_test.conn.get_setup()
# When X upgrades, we can probably manage to change this test :-)
assert setup.protocol_major_version == 11
assert setup.protocol_minor_version == 0
def test_get_screen_pointers(self, xproto_test):
screens = xproto_test.conn.get_screen_pointers()
assert len(screens) == 1
screen = screens[0]
assert ffi.typeof(screen) is ffi.typeof("xcb_screen_t *")
assert screen.root == xproto_test.default_screen.root
assert screen.width_in_pixels == xproto_test.width
assert screen.height_in_pixels == xproto_test.height
assert screen.root_depth == xproto_test.depth
def test_seq_increases(self, xproto_test):
# If this test starts failing because the sequence numbers don't mach,
# that's probably because you added a new test that imports a new X
# extension. When that happens, every new connection automatically does
# a QueryExtention for each new ext that has been imported, so the
# squence numbers go up by one.
#
# i.e:
# xproto setup query = seqno 0
# xtest setup query = seqno 1
assert xproto_test.xproto.GetInputFocus().sequence == 7
assert xproto_test.xproto.GetInputFocus().sequence == 8
def test_discard_sequence(self, xproto_test):
cookie = xproto_test.xproto.GetInputFocus()
cookie.discard_reply()
# this hangs if you leave it in, because the reply really was discarded
# assert cookie.reply()
def test_invalid(self, xproto_test):
with pytest.raises(xcffib.ConnectionException):
xcffib.Connection("notadisplay")
def test_list_extensions(self, xproto_test):
reply = xproto_test.conn.core.ListExtensions().reply()
exts = [ext.name.to_string() for ext in reply.names]
assert "XVideo" in exts
def test_create_window(self, xproto_test):
wid = xproto_test.conn.generate_id()
cookie = xproto_test.create_window(wid=wid)
cookie = xproto_test.xproto.GetGeometry(wid)
reply = cookie.reply()
assert reply.x == 0
assert reply.y == 0
assert reply.width == 1
assert reply.height == 1
def test_wait_for_nonexistent_request(self, xproto_test):
with pytest.raises(xcffib.XcffibException):
xproto_test.conn.wait_for_reply(10)
def test_no_windows(self, xproto_test):
# Make sure there aren't any windows in the root window. This mostly
# just exists to make sure people aren't somehow mistakenly running a
# test in their own X session, which could corrupt results.
reply = xproto_test.xproto.QueryTree(xproto_test.default_screen.root).reply()
assert reply.children_len == 0
assert len(reply.children) == 0
def test_create_window_creates_window(self, xproto_test):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid=wid)
reply = xproto_test.xproto.QueryTree(xproto_test.default_screen.root).reply()
assert reply.children_len == 1
assert len(reply.children) == 1
assert reply.children[0] == wid
def test_checking_unchecked_fails(self, xproto_test):
with pytest.raises(AssertionError):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid)
xproto_test.xproto.QueryTreeUnchecked(
xproto_test.default_screen.root
).check()
def test_checking_default_checked_fails(self, xproto_test):
with pytest.raises(AssertionError):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid)
cookie = xproto_test.xproto.QueryTree(xproto_test.default_screen.root)
cookie.check()
def test_checking_foreced_checked_succeeds(self, xproto_test):
wid = xproto_test.conn.generate_id()
cookie = xproto_test.create_window(wid, is_checked=True)
cookie.check()
def test_create_window_generates_event(self, xproto_test):
xproto_test.xeyes()
e = xproto_test.conn.wait_for_event()
assert isinstance(e, xcffib.xproto.CreateNotifyEvent)
def test_query_invalid_wid_generates_error(self, xproto_test):
with pytest.raises(xcffib.xproto.WindowError):
# query a bad WINDOW
xproto_test.xproto.QueryTree(0xF00).reply()
def test_OpenFont(self, xproto_test):
fid = xproto_test.conn.generate_id()
xproto_test.xproto.OpenFont(fid, len("cursor"), "cursor")
def test_ConfigureWindow(self, xproto_test):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid=wid)
xproto_test.xproto.ConfigureWindowChecked(wid, 0, []).check()
def test_external_ConfigureWindow(self, xproto_test):
xproto_test.xeyes()
e = xproto_test.conn.wait_for_event()
xproto_test.xproto.ConfigureWindowChecked(e.window, 0, []).check()
xproto_test.xproto.DestroyWindowChecked(e.window).check()
def test_ChangeProperty_WM_NAME(self, xproto_test):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid=wid)
title = "test"
xproto_test.xproto.ChangeProperty(
xcffib.xproto.PropMode.Replace,
wid,
xcffib.xproto.Atom.WM_NAME,
xcffib.xproto.Atom.STRING,
8,
len(title),
title,
)
reply = xproto_test.xproto.GetProperty(
False,
wid,
xcffib.xproto.Atom.WM_NAME,
xcffib.xproto.GetPropertyType.Any,
0,
1,
).reply()
assert reply.value.to_string() == title
def test_ChangeProperty_NET_WM_NAME(self, xproto_test):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid=wid)
net_wm_name = xproto_test.intern("_NET_WM_NAME")
utf8_string = xproto_test.intern("UTF8_STRING")
title_bytes = b"test\xc2\xb7"
title_string = "test\u00B7"
# First check with an object already encoded as bytes
xproto_test.xproto.ChangeProperty(
xcffib.xproto.PropMode.Replace,
wid,
net_wm_name,
utf8_string,
8,
len(title_bytes),
title_bytes,
)
reply = xproto_test.xproto.GetProperty(
False, wid, net_wm_name, xcffib.xproto.GetPropertyType.Any, 0, (2 ** 32) - 1
).reply()
print(reply.value.buf())
assert reply.value.buf() == title_bytes
print(reply.value.to_utf8())
assert reply.value.to_utf8() == title_string
# Also check with a unicode string
xproto_test.xproto.ChangeProperty(
xcffib.xproto.PropMode.Replace,
wid,
net_wm_name,
utf8_string,
8,
len(title_string.encode("utf-8")),
title_string,
)
reply = xproto_test.xproto.GetProperty(
False, wid, net_wm_name, xcffib.xproto.GetPropertyType.Any, 0, (2 ** 32) - 1
).reply()
assert reply.value.buf() == title_bytes
assert reply.value.to_utf8() == title_string
def test_ChangeProperty_WM_PROTOCOLS(self, xproto_test):
wid = xproto_test.conn.generate_id()
xproto_test.create_window(wid=wid)
wm_protocols = xproto_test.intern("WM_PROTOCOLS")
wm_delete_window = xproto_test.intern("WM_DELETE_WINDOW")
xproto_test.xproto.ChangeProperty(
xcffib.xproto.PropMode.Replace,
wid,
wm_protocols,
xcffib.xproto.Atom.ATOM,
32,
1,
(wm_delete_window,),
)
reply = xproto_test.xproto.GetProperty(
False, wid, wm_protocols, xcffib.xproto.Atom.ATOM, 0, 1
).reply()
assert reply.value.to_atoms() == (wm_delete_window,)
wm_take_focus = xproto_test.intern("WM_TAKE_FOCUS")
xproto_test.xproto.ChangeProperty(
xcffib.xproto.PropMode.Replace,
wid,
wm_protocols,
xcffib.xproto.Atom.ATOM,
32,
1,
(wm_take_focus,),
)
reply = xproto_test.xproto.GetProperty(
False, wid, wm_protocols, xcffib.xproto.Atom.ATOM, 0, 1
).reply()
assert reply.value.to_atoms() == (wm_take_focus,)
def test_GetAtomName(self, xproto_test):
wm_protocols = "WM_PROTOCOLS"
atom = xproto_test.intern(wm_protocols)
atom_name = xproto_test.xproto.GetAtomName(atom).reply().name
assert atom_name.to_string() == wm_protocols
def test_KillClient(self, xproto_test):
xproto_test.xeyes()
e1 = xproto_test.conn.wait_for_event()
xproto_test.xproto.KillClient(e1.window)
# one is MapRequest and the other is DestroyNotify, they may be in
# either order
for _ in range(2):
xproto_test.conn.flush()
k1 = xproto_test.conn.wait_for_event()
if isinstance(k1, xcffib.xproto.DestroyNotifyEvent):
assert e1.window == k1.window
return
assert False, "no DestroyNotifyEvent"
def test_connect(self, xproto_test):
c = xcffib.connect()
c.invalid()
assert c.has_error() == 0
c.disconnect()
def test_auth_connect(self, xproto_test):
authname = b"MIT-MAGIC-COOKIE-1"
authdata = b"\xa5\xcf\x95\xfa\x19\x49\x03\x60\xaf\xe4\x1e\xcd\xa3\xe2\xad\x47"
authstr = authname + b":" + authdata
conn = xcffib.connect(display=os.environ["DISPLAY"], auth=authstr)
assert conn.get_setup().roots[0].root > 0
# This is an adaptation of the test from #27
def test_build_atom_cache(self, xproto_test):
# This will hold the forward *and* reverse lookups for any given atom
atoms = {}
cookies = []
# Batch the replies by creating a list of cookies first:
for i in range(1, 10000):
c = xproto_test.conn.core.GetAtomName(i)
cookies.append((i, c))
for i, c in cookies:
try:
name = c.reply().name.to_string()
except xcffib.xproto.BadAtom:
continue
atoms.update({i: name}) # Lookup by number
atoms.update({name: i}) # Lookup by name
def test_wrap(self, xproto_test):
c = xcffib.connect()
c.invalid()
c2 = xcffib.wrap(xcffib.ffi.cast("long", c._conn))
c2.invalid()
c2.disconnect()
xcffib-1.5.0/test/test_crazy_window_script.py 0000664 0000000 0000000 00000012714 14470210147 0021456 0 ustar 00root root 0000000 0000000 # Copyright 2012 Florian Mounier
# Copyright 2014 Sean Vig
# Copyright 2014 Tycho Andersen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
This is mostly stolen from qtile's tests/scripts/window.py
"""
import os
import sys
import struct
import time
import xcffib
import xcffib.xproto
from xcffib.xproto import EventMask
class TestWindow:
def test_the_script(self, xcffib_test):
NAME = "one"
for i in range(20):
try:
conn = xcffib.connect(os.environ["DISPLAY"])
except xcffib.ConnectionException:
time.sleep(0.1)
continue
except Exception as v:
print("Error opening test window: ", type(v), v, file=sys.stderr)
sys.exit(1)
break
else:
print(
"Could not open window on display %s" % (sys.argv[1]), file=sys.stderr
)
sys.exit(1)
screen = conn.get_setup().roots[conn.pref_screen]
window = conn.generate_id()
background = (
conn.core.AllocColor(screen.default_colormap, 0x2828, 0x8383, 0xCECE)
.reply()
.pixel
) # Color "#2883ce"
conn.core.CreateWindow(
xcffib.CopyFromParent,
window,
screen.root,
100,
100,
100,
100,
1,
xcffib.xproto.WindowClass.InputOutput,
screen.root_visual,
xcffib.xproto.CW.BackPixel | xcffib.xproto.CW.EventMask,
[
background,
xcffib.xproto.EventMask.StructureNotify
| xcffib.xproto.EventMask.Exposure,
],
)
conn.core.ChangeProperty(
xcffib.xproto.PropMode.Replace,
window,
xcffib.xproto.Atom.WM_NAME,
xcffib.xproto.Atom.STRING,
8,
len(NAME),
NAME,
)
wm_protocols = "WM_PROTOCOLS"
wm_protocols = (
conn.core.InternAtom(0, len(wm_protocols), wm_protocols).reply().atom
)
wm_delete_window = "WM_DELETE_WINDOW"
wm_delete_window = (
conn.core.InternAtom(0, len(wm_delete_window), wm_delete_window)
.reply()
.atom
)
conn.core.ChangeProperty(
xcffib.xproto.PropMode.Replace,
window,
wm_protocols,
xcffib.xproto.Atom.ATOM,
32,
1,
[wm_delete_window],
)
conn.core.ConfigureWindow(
window,
xcffib.xproto.ConfigWindow.X
| xcffib.xproto.ConfigWindow.Y
| xcffib.xproto.ConfigWindow.Width
| xcffib.xproto.ConfigWindow.Height
| xcffib.xproto.ConfigWindow.BorderWidth,
[0, 0, 100, 100, 1],
)
conn.core.MapWindow(window)
conn.flush()
conn.core.ConfigureWindow(
window,
xcffib.xproto.ConfigWindow.X
| xcffib.xproto.ConfigWindow.Y
| xcffib.xproto.ConfigWindow.Width
| xcffib.xproto.ConfigWindow.Height
| xcffib.xproto.ConfigWindow.BorderWidth,
[0, 0, 100, 100, 1],
)
# now kill the window from the "wm" side via WM_DELETE_WINDOW protocol
WM_PROTOCOLS = (
xcffib_test.conn.core.InternAtom(False, len("WM_PROTOCOLS"), "WM_PROTOCOLS")
.reply()
.atom
)
WM_DELETE_WINDOW = (
xcffib_test.conn.core.InternAtom(
False, len("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW"
)
.reply()
.atom
)
vals = [
33, # ClientMessageEvent
32, # Format
0,
window,
WM_PROTOCOLS,
WM_DELETE_WINDOW,
xcffib.xproto.Time.CurrentTime,
0,
0,
0,
]
e = struct.pack("BBHII5I", *vals)
xcffib_test.conn.core.SendEvent(False, window, EventMask.NoEvent, e)
xcffib_test.conn.flush()
while 1:
conn.flush()
event = conn.wait_for_event()
if event.__class__ == xcffib.xproto.ClientMessageEvent:
atom = conn.core.GetAtomName(event.type).reply().name.to_string()
print(atom)
if atom == "WM_PROTOCOLS":
break
# This test passes if it gets all the way here without dying :-)
xcffib-1.5.0/test/test_fakeinput.py 0000664 0000000 0000000 00000001617 14470210147 0017341 0 ustar 00root root 0000000 0000000 import xcffib
import xcffib.xproto
import xcffib.xtest
def test_fakeinput(xcffib_test):
xtest = xcffib_test.conn(xcffib.xtest.key)
setup = xcffib_test.conn.get_setup()
screen = setup.roots[0]
def test(x, y):
# motion
xtest.FakeInput(
6,
0,
xcffib.xproto.Time.CurrentTime,
screen.root,
x,
y,
0)
# press
xtest.FakeInput(
4,
1,
xcffib.xproto.Time.CurrentTime,
screen.root,
0,
0,
0)
# release
xtest.FakeInput(
5,
1,
xcffib.xproto.Time.CurrentTime,
screen.root,
2,
2,
0)
xcffib_test.conn.flush()
test(50, 10)
# we shouldn't get any errors
xcffib_test.conn.poll_for_event()
xcffib-1.5.0/test/test_python_code.py 0000664 0000000 0000000 00000016342 14470210147 0017667 0 ustar 00root root 0000000 0000000 # Copyright 2014 Tycho Andersen
# Copyright 2014 Sean Vig
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import xcffib
import xcffib.xproto
import xcffib.xinput
import xcffib.randr
import os
import struct
import sys
from xcffib.ffi import ffi
from xcffib.xproto import EventMask
from .conftest import XcffibTest
class TestPythonCode:
def test_struct_pack_uses_List(self):
# suppose we have a list of ints...
ints = struct.pack("=IIII", *range(4))
# Unpacker wants a cffi.cdata
cffi_ints = xcffib.ffi.new('char[]', ints)
l = xcffib.List(xcffib.CffiUnpacker(cffi_ints), "I", count=4)
ints2 = struct.pack("=IIII", *l)
# after packing and unpacking, we should still have those ints
assert ints == ints2
def test_union_pack(self):
data = struct.pack("=" + ("b" * 20), *range(20))
cffi_data = xcffib.ffi.new('char[]', data)
cm = xcffib.xproto.ClientMessageData(xcffib.CffiUnpacker(cffi_data))
for actual, expected in zip(range(20), cm.data8):
assert actual == expected, actual
if sys.byteorder == "little":
assert cm.data32[0] == 0x03020100
assert cm.data32[1] == 0x07060504
assert cm.data32[2] == 0x0b0a0908
elif sys.byteorder == "big":
assert cm.data32[0] == 0x00010203
assert cm.data32[1] == 0x04050607
assert cm.data32[2] == 0x08090a0b
else:
raise Exception("unknown byte order?")
def test_offset_map(self):
om = xcffib.OffsetMap({0: "Event0,0"})
om.add(1, 0, {0: "Event1,0", 1: "Event1,1"})
assert om[0] == "Event0,0"
assert om[1] == "Event1,0"
assert om[2] == "Event1,1"
om.add(10, 20, {5: "ExtensionEvent20,5", 6: "ExtensionEvent20,6"})
assert om.get_extension_item(20, 5) == "ExtensionEvent20,5"
assert om.get_extension_item(20, 6) == "ExtensionEvent20,6"
def test_create_ClientMessageEvent(self, xcffib_test):
wm_protocols = xcffib_test.intern("WM_PROTOCOLS")
wm_delete_window = xcffib_test.intern("WM_DELETE_WINDOW")
# should be exactly 20 bytes
data = [
wm_delete_window,
xcffib.xproto.Time.CurrentTime,
0,
0,
0,
]
union = xcffib.xproto.ClientMessageData.synthetic(data, "I" * 5)
assert list(union.data32) == data
wid = xcffib_test.conn.generate_id()
xcffib_test.create_window(wid=wid)
wm_protocols = xcffib_test.intern("WM_PROTOCOLS")
wm_delete_window = xcffib_test.intern("WM_DELETE_WINDOW")
e = xcffib.xproto.ClientMessageEvent.synthetic(
format=32,
window=wid,
type=wm_protocols,
data=union
)
xcffib_test.xproto.SendEvent(False, wid, EventMask.NoEvent, e.pack())
xcffib_test.conn.flush()
e = xcffib_test.conn.wait_for_event()
assert isinstance(e, xcffib.xproto.ClientMessageEvent)
assert e.window == wid
assert list(e.data.data32) == data
def test_pack_from_event(self, xcffib_test):
wm_protocols = xcffib_test.intern("WM_PROTOCOLS")
wm_delete_window = xcffib_test.intern("WM_DELETE_WINDOW")
wid = xcffib_test.conn.generate_id()
# should be exactly 20 bytes
data = [
wm_delete_window,
xcffib.xproto.Time.CurrentTime,
0,
0,
0,
]
union = xcffib.xproto.ClientMessageData.synthetic(data, "I" * 5)
e = xcffib.xproto.ClientMessageEvent.synthetic(
format=32,
window=wid,
type=wm_protocols,
data=union
)
e2 = xcffib.xproto.ClientMessageEvent(e)
def test_get_image(self, xcffib_test):
# adapted from: https://gist.github.com/liftoff/4741790
setup = xcffib_test.conn.get_setup()
screen = setup.roots[0]
width = screen.width_in_pixels
height = screen.height_in_pixels
root = screen.root
# GetImage requires an output format as the first arg. We want ZPixmap:
output_format = xcffib.xproto.ImageFormat.ZPixmap
plane_mask = 2**32 - 1 # No idea what this is but it works!
reply = xcffib_test.conn.core.GetImage(
output_format, root, 0, 0, width, height, plane_mask).reply()
reply.data.buf()
def test_ge_generic_event_hoist(self, xcffib_test):
"""Tests the ability to hoist events to the correct extension event."""
# Create a bytearray representing a BarrierHitEvent from XInput
B_HIT_EVENT = struct.pack(
"=BBHIHHIIIIIIIIH2xiiQQ",
35, # response_type
131, # extension
1, # sequence
9, # length
25, # event_type
2, # device_id
0, # time
1, # event_id
0, # root
0, # event
0, # barrier
1, # full_sequence
0, # d_time
0, # flags
11, # source_id
100 << 16, # root_x
200 << 16, # root_y
0, # dx
0 # dy
)
# Create cdata from the bytearray and cast it to a generic reply
cdata = ffi.new("char x[72]", B_HIT_EVENT)
generic_reply = ffi.cast("xcb_generic_reply_t *", cdata)
# Pass the reply to our hoist_event method
event = xcffib_test.conn.hoist_event(generic_reply)
assert isinstance(event, xcffib.xinput.BarrierHitEvent)
assert event.root_x >> 16 == 100
assert event.root_y >> 16 == 200
def test_List_to_string(self, xcffib_test):
xrandr = xcffib_test.conn(xcffib.randr.key)
try:
setup = xcffib_test.conn.get_setup()
for screen in setup.roots:
scrn_rsrcs = xrandr.GetScreenResources(screen.root).reply()
for output in scrn_rsrcs.outputs:
info = xrandr.GetOutputInfo(output, xcffib.XCB_CURRENT_TIME).reply()
print(info.name.to_string())
assert info.name.to_string() == 'screen'
finally:
xcffib_test.conn.disconnect()
class TestXcffibTestGenerator:
def test_XcffibTest_generator(self):
try:
old_display = os.environ['DISPLAY']
except KeyError:
old_display = ""
# use some non-default width/height
with XcffibTest(width=1001, height=502) as test:
assert os.environ['DISPLAY'] != old_display
setup = test.conn.get_setup()
screen = setup.roots[0]
width = screen.width_in_pixels
height = screen.height_in_pixels
assert width == 1001
assert height == 502
xcffib-1.5.0/test/test_render.py 0000664 0000000 0000000 00000006131 14470210147 0016626 0 ustar 00root root 0000000 0000000 import xcffib
import xcffib.render
import xcffib.xproto
from xcffib.xproto import CW, EventMask, ExposeEvent, WindowClass
WIDTH = 50
HEIGHT = 50
def double_to_fixed(num):
return int(num * 65536)
def find_format(screen, depth, visual):
for d in screen.depths:
if d.depth == depth:
for v in d.visuals:
if v.visual == visual:
return v.format
class TestConnection:
def test_CreateLinearGradient(self, xproto_test):
conn = xproto_test.conn
setup = conn.get_setup()
root = setup.roots[0].root
depth = setup.roots[0].root_depth
visual = setup.roots[0].root_visual
black = setup.roots[0].black_pixel
conn.render = conn(xcffib.render.key)
conn.render.QueryVersion(xcffib.render.MAJOR_VERSION, xcffib.render.MINOR_VERSION)
window = conn.generate_id()
conn.core.CreateWindow(
depth,
window,
root,
0,
0,
640,
480,
0,
WindowClass.InputOutput,
visual,
CW.BackPixel | CW.EventMask,
[black, EventMask.Exposure | EventMask.KeyPress],
)
conn.core.MapWindow(window)
conn.flush()
while True:
event = conn.wait_for_event()
if isinstance(event, ExposeEvent):
cookie = conn.render.QueryPictFormats()
reply = cookie.reply()
fmt = find_format(reply.screens[0], depth, visual)
# Create picture from the window
pic_window = conn.generate_id()
conn.render.CreatePicture(pic_window, window, fmt, 0, [])
# Create the linear gradient
pic_gradient = conn.generate_id()
conn.render.CreateLinearGradientChecked(
pic_gradient,
xcffib.render.POINTFIX.synthetic(0, 0),
xcffib.render.POINTFIX.synthetic(double_to_fixed(WIDTH), double_to_fixed(HEIGHT)),
2,
[0, double_to_fixed(1)],
[
xcffib.render.COLOR.synthetic(0, 0, 0, 0xffff), # Solid black
xcffib.render.COLOR.synthetic(0xffff, 0xffff, 0xffff, 0xffff), # Solid white
],
).check()
# Render the gradient onto the window
conn.render.Composite(
xcffib.render.PictOp.Src,
pic_gradient,
0,
pic_window,
0, 0,
0, 0,
0, 0,
WIDTH, HEIGHT
)
img = conn.core.GetImage(
xcffib.xproto.ImageFormat.ZPixmap,
window,
0, 0,
WIDTH, HEIGHT,
0xffffffff
).reply()
conn.flush()
assert img.data[0:2] == [0, 0]
assert img.data[-2:] == [255, 255]
break
xcffib-1.5.0/xcffib.cabal 0000664 0000000 0000000 00000005535 14470210147 0015213 0 ustar 00root root 0000000 0000000 name: xcffib
version: 1.5.0
synopsis: A cffi-based python binding for X
homepage: http://github.com/tych0/xcffib
license: OtherLicense
license-file: LICENSE
author: Tycho Andersen
maintainer: Tycho Andersen
category: X11
build-type: Simple
cabal-version: >=1.10
bug-reports: https://github.com/tych0/xcffib/issues
description: A cffi-based python binding for X, comparable to xpyb
extra-source-files: test/generator/*.py,
test/generator/*.xml,
-- cabal's wildcarding is broken if the filename contains
-- extra dots:
-- https://github.com/haskell/cabal/issues/784
test/generator/*.7.py,
test/generator/*.7.xml
source-repository head
type: git
location: git://github.com/tych0/xcffib.git
library
build-depends: base ==4.*,
xcb-types >= 0.13.0,
language-python >= 0.5.6,
filepath,
filemanip,
split,
containers,
mtl >= 2.1,
attoparsec,
bytestring,
either
hs-source-dirs: generator
exposed-modules: Data.XCB.Python.Parse,
Data.XCB.Python.PyHelpers
ghc-options: -Wall
default-language: Haskell2010
executable xcffibgen
main-is: xcffibgen.hs
hs-source-dirs: generator
build-depends: base ==4.*,
xcffib,
language-python >= 0.5.6,
split,
xcb-types >= 0.13.0,
optparse-applicative >= 0.13,
filepath,
filemanip,
directory >= 1.2,
containers,
mtl >= 2.1,
attoparsec,
bytestring,
either
if impl(ghc < 8.0)
build-depends: semigroups
other-modules: Data.XCB.Python.Parse,
Data.XCB.Python.PyHelpers
ghc-options: -Wall
default-language: Haskell2010
test-suite PyHelpersTests
hs-source-dirs: test
main-is: PyHelpersTests.hs
type: exitcode-stdio-1.0
build-depends: base ==4.*,
xcffib,
language-python >= 0.5.6,
HUnit,
test-framework,
test-framework-hunit
default-language: Haskell2010
test-suite GeneratorTests.hs
hs-source-dirs: test
main-is: GeneratorTests.hs
type: exitcode-stdio-1.0
build-depends: base ==4.*,
xcffib,
xcb-types >= 0.13.0,
language-python >= 0.5.6,
HUnit,
test-framework,
test-framework-hunit,
filepath
default-language: Haskell2010