pax_global_header00006660000000000000000000000064144702101470014512gustar00rootroot0000000000000052 comment=ff5cc8863672d91a8ea41be0d898ccf57c4352fb xcffib-1.5.0/000077500000000000000000000000001447021014700127565ustar00rootroot00000000000000xcffib-1.5.0/.github/000077500000000000000000000000001447021014700143165ustar00rootroot00000000000000xcffib-1.5.0/.github/workflows/000077500000000000000000000000001447021014700163535ustar00rootroot00000000000000xcffib-1.5.0/.github/workflows/ci.yaml000066400000000000000000000021611447021014700176320ustar00rootroot00000000000000name: 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.yaml000066400000000000000000000004631447021014700206620ustar00rootroot00000000000000name: "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/.gitignore000066400000000000000000000004211447021014700147430ustar00rootroot00000000000000build 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/LICENSE000066400000000000000000000236761447021014700140010ustar00rootroot00000000000000 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.in000066400000000000000000000001531447021014700145130ustar00rootroot00000000000000include README.md include LICENSE exclude xcffib/_ffi.py include test/__init__.py include test/conftest.py xcffib-1.5.0/Makefile000066400000000000000000000060471447021014700144250ustar00rootroot00000000000000AUTOPEP8=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.md000066400000000000000000000130051447021014700142340ustar00rootroot00000000000000# xcffib [![Build Status](https://github.com/tych0/xcffib/workflows/ci/badge.svg?branch=master)](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.config000066400000000000000000000001211447021014700152010ustar00rootroot00000000000000repository hackage.haskell.org url: http://hackage.haskell.org/ secure: True xcffib-1.5.0/generator/000077500000000000000000000000001447021014700147445ustar00rootroot00000000000000xcffib-1.5.0/generator/Data/000077500000000000000000000000001447021014700156155ustar00rootroot00000000000000xcffib-1.5.0/generator/Data/XCB/000077500000000000000000000000001447021014700162315ustar00rootroot00000000000000xcffib-1.5.0/generator/Data/XCB/Python/000077500000000000000000000000001447021014700175125ustar00rootroot00000000000000xcffib-1.5.0/generator/Data/XCB/Python/Parse.hs000066400000000000000000001040701447021014700211220ustar00rootroot00000000000000{- - 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.hs000066400000000000000000000142401447021014700217620ustar00rootroot00000000000000{- - 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.hs000066400000000000000000000036471447021014700172450ustar00rootroot00000000000000{- - 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/000077500000000000000000000000001447021014700142435ustar00rootroot00000000000000xcffib-1.5.0/module/__init__.py000066400000000000000000000647451447021014700163740ustar00rootroot00000000000000# 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.py000066400000000000000000000213661447021014700153710ustar00rootroot00000000000000# 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.py000066400000000000000000000104101447021014700162660ustar00rootroot00000000000000# 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.py000066400000000000000000000022311447021014700164560ustar00rootroot00000000000000# 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.txt000066400000000000000000000000341447021014700162370ustar00rootroot00000000000000flake8 autopep8 cffi>=0.8.2 xcffib-1.5.0/setup.py000066400000000000000000000056411447021014700144760ustar00rootroot00000000000000#!/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/000077500000000000000000000000001447021014700137355ustar00rootroot00000000000000xcffib-1.5.0/test/GeneratorTests.hs000066400000000000000000000041371447021014700172470ustar00rootroot00000000000000{- - 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.hs000066400000000000000000000025601447021014700172320ustar00rootroot00000000000000{- - 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__.py000066400000000000000000000000001447021014700160340ustar00rootroot00000000000000xcffib-1.5.0/test/conftest.py000066400000000000000000000047171447021014700161450ustar00rootroot00000000000000# 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.cfg000066400000000000000000000004601447021014700155700ustar00rootroot00000000000000# 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/000077500000000000000000000000001447021014700157235ustar00rootroot00000000000000xcffib-1.5.0/test/generator/enum.py000066400000000000000000000017271447021014700172500ustar00rootroot00000000000000import 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.xml000066400000000000000000000037141447021014700174160ustar00rootroot00000000000000 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.py000066400000000000000000000015411447021014700174270ustar00rootroot00000000000000import 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.xml000066400000000000000000000005351447021014700176010ustar00rootroot00000000000000 xcffib-1.5.0/test/generator/event.py000066400000000000000000000034301447021014700174160ustar00rootroot00000000000000import 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.xml000066400000000000000000000014511447021014700175670ustar00rootroot00000000000000 xcffib-1.5.0/test/generator/eventstruct.py000066400000000000000000000011601447021014700206610ustar00rootroot00000000000000import 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.xml000066400000000000000000000026511447021014700210370ustar00rootroot00000000000000 num_events num_classes xcffib-1.5.0/test/generator/no_sequence.py000066400000000000000000000017161447021014700206060ustar00rootroot00000000000000import 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.xml000066400000000000000000000002551447021014700207530ustar00rootroot00000000000000 31 xcffib-1.5.0/test/generator/randr.py000066400000000000000000000054651447021014700174150ustar00rootroot00000000000000import 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.xml000066400000000000000000000032261447021014700175560ustar00rootroot00000000000000 pending_len pending_nparams current_len current_nparams xcffib-1.5.0/test/generator/render.py000066400000000000000000000042011447021014700175510ustar00rootroot00000000000000import 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.xml000066400000000000000000000014371447021014700177310ustar00rootroot00000000000000 xcffib-1.5.0/test/generator/render_1.7.py000066400000000000000000000023651447021014700201470ustar00rootroot00000000000000import 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.xml000066400000000000000000000043421447021014700203140ustar00rootroot00000000000000 16 32 48 xcffib-1.5.0/test/generator/request.py000066400000000000000000000011521447021014700177640ustar00rootroot00000000000000import 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.xml000066400000000000000000000063241447021014700201420ustar00rootroot00000000000000 Creates a window xcffib-1.5.0/test/generator/request_reply.py000066400000000000000000000032031447021014700211760ustar00rootroot00000000000000import 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.xml000066400000000000000000000007251447021014700213540ustar00rootroot00000000000000 name_len names_len xcffib-1.5.0/test/generator/struct.py000066400000000000000000000036631447021014700176310ustar00rootroot00000000000000import 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.xml000066400000000000000000000012351447021014700177720ustar00rootroot00000000000000 axes_len xcffib-1.5.0/test/generator/switch.py000066400000000000000000000070041447021014700175770ustar00rootroot00000000000000import 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.xml000066400000000000000000000055201447021014700177500ustar00rootroot00000000000000 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.py000066400000000000000000000064741447021014700201150ustar00rootroot00000000000000import 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.xml000066400000000000000000000073031447021014700202550ustar00rootroot00000000000000 pattern_len properties_len name_len get matching font names and information xcffib-1.5.0/test/generator/union.py000066400000000000000000000012411447021014700174230ustar00rootroot00000000000000import 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.xml000066400000000000000000000006501447021014700175760ustar00rootroot00000000000000 20 10 5 xcffib-1.5.0/test/generator/xinput.xml000066400000000000000000000054661447021014700200070ustar00rootroot00000000000000 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.py000066400000000000000000000031051447021014700202140ustar00rootroot00000000000000import 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.xml000066400000000000000000000050541447021014700203710ustar00rootroot00000000000000 0 0 xcffib-1.5.0/test/test_connection.py000066400000000000000000000265431447021014700175170ustar00rootroot00000000000000# 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.py000066400000000000000000000127141447021014700214560ustar00rootroot00000000000000# 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.py000066400000000000000000000016171447021014700173410ustar00rootroot00000000000000import 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.py000066400000000000000000000163421447021014700176670ustar00rootroot00000000000000# 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.py000066400000000000000000000061311447021014700166260ustar00rootroot00000000000000import 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.cabal000066400000000000000000000055351447021014700152130ustar00rootroot00000000000000name: 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