aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--COPYING15
-rw-r--r--DEPENDENCIES1
-rw-r--r--LICENSE683
-rw-r--r--Makefile139
-rw-r--r--README2
-rw-r--r--arg.h (renamed from src/arg.h)0
-rw-r--r--communication.c159
-rw-r--r--communication.h (renamed from src/communication.h)73
-rw-r--r--config.mk12
-rw-r--r--coopgammad.1 (renamed from doc/coopgammad.1)1
-rw-r--r--coopgammad.c875
-rw-r--r--servers-coopgamma.c524
-rw-r--r--servers-coopgamma.h (renamed from src/servers/coopgamma.h)51
-rw-r--r--servers-crtc.c312
-rw-r--r--servers-crtc.h (renamed from src/servers/crtc.h)43
-rw-r--r--servers-gamma.c381
-rw-r--r--servers-gamma.h60
-rw-r--r--servers-kernel.c363
-rw-r--r--servers-kernel.h64
-rw-r--r--servers-master.c370
-rw-r--r--servers-master.h17
-rw-r--r--src/communication.c175
-rw-r--r--src/coopgammad.c895
-rw-r--r--src/servers/coopgamma.c558
-rw-r--r--src/servers/crtc.c325
-rw-r--r--src/servers/gamma.c398
-rw-r--r--src/servers/gamma.h85
-rw-r--r--src/servers/kernel.c384
-rw-r--r--src/servers/kernel.h85
-rw-r--r--src/servers/master.c394
-rw-r--r--src/servers/master.h36
-rw-r--r--src/state.c638
-rw-r--r--src/types/filter.c144
-rw-r--r--src/types/filter.h138
-rw-r--r--src/types/message.c572
-rw-r--r--src/types/message.h160
-rw-r--r--src/types/output.c335
-rw-r--r--src/types/output.h308
-rw-r--r--src/types/ramps.c98
-rw-r--r--src/types/ramps.h102
-rw-r--r--src/types/ring.c224
-rw-r--r--src/types/ring.h152
-rw-r--r--src/util.c316
-rw-r--r--src/util.h115
-rw-r--r--state.c612
-rw-r--r--state.h (renamed from src/state.h)61
-rw-r--r--types-filter.c125
-rw-r--r--types-filter.h108
-rw-r--r--types-message.c561
-rw-r--r--types-message.h133
-rw-r--r--types-output.c322
-rw-r--r--types-output.h275
-rw-r--r--types-ramps.c86
-rw-r--r--types-ramps.h75
-rw-r--r--types-ring.c193
-rw-r--r--types-ring.h131
-rw-r--r--util.c274
-rw-r--r--util.h81
59 files changed, 6229 insertions, 7593 deletions
diff --git a/.gitignore b/.gitignore
index 72a83fb..4197d01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,3 @@
-bin/
-obj/
\#*\#
.\#*
*~
@@ -13,3 +11,4 @@ obj/
*.dvi
*.ps
*.info
+/coopgammad
diff --git a/COPYING b/COPYING
deleted file mode 100644
index 900f61b..0000000
--- a/COPYING
+++ /dev/null
@@ -1,15 +0,0 @@
-coopgammad -- Cooperative gamma server
-Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/DEPENDENCIES b/DEPENDENCIES
index 7e4084b..c449ab4 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -18,4 +18,3 @@ INSTALL DEPENDENCIES:
make
coreutils
-
diff --git a/LICENSE b/LICENSE
index 94a9ed0..c2c5e80 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,674 +1,15 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
+ISC License
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+© 2016, 2019 Mattias Andrée <maandree@kth.se>
- Preamble
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- <program> Copyright (C) <year> <name of author>
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
index 96fdd0f..065ed9e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,115 +1,52 @@
-PREFIX = /usr
-BINDIR = $(PREFIX)/bin
-DATADIR = $(PREFIX)/share
-MANDIR = $(DATADIR)/man
-MAN1DIR = $(MANDIR)/man1
-LICENSEDIR = $(DATADIR)/licenses
+.POSIX:
-PKGNAME = coopgammad
-COMMAND = coopgammad
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
-KERNEL = $(shell uname | tr '[A-Z]_' '[a-z]-')
+XCPPFLAGS = -D'PKGNAME="$(PKGNAME)"' -D'COMMAND="$(COMMAND)"'
-SRC = \
- coopgammad \
- util \
- communication \
- state \
- servers/master \
- servers/kernel \
- servers/crtc \
- servers/gamma \
- servers/coopgamma \
- types/filter \
- types/output \
- types/ramps \
- types/message \
- types/ring
+PARTS =\
+ communication\
+ state\
+ util\
+ servers-master\
+ servers-kernel\
+ servers-crtc\
+ servers-gamma\
+ servers-coopgamma\
+ types-filter\
+ types-output\
+ types-ramps\
+ types-message\
+ types-ring
-OPTIMISE = -O2
+OBJ = $(PARTS:=.o) coopgammad.c
-WARN = -Wall -Wextra -pedantic
+HDR = $(PARTS:=.h) arg.h
-CPP_linux = -DHAVE_LINUX_PROCFS
-CPP_linux-libre = $(CPP_linux)
+all: coopgammad
+$(OBJ): $(@:.o=.c) $(HDR)
-CCFLAGS = -std=c99 $(WARN) $(FFLAGS) $(OPTIMISE)
-LDFLAGS = $(OPTIMISE) -lgamma
-CPPFLAGS = -D'PKGNAME="$(PKGNAME)"' -D'COMMAND="$(COMMAND)"' -D_XOPEN_SOURCE=700 $(CPP_$(KERNEL))
-ifdef USE_VALGRIND
-CPPFLAGS += -DUSE_VALGRIND
-endif
+.c.o:
+ $(CC) -c -o $@ $< $(XCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+coopgammad: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LDFLAGS)
+install: coopgammad
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1"
+ cp -- coopgammad "$(DESTDIR)$(PREFIX)/bin/coopgammad"
+ cp -- coopgammad.1 "$(DESTDIR)$(MANPREFIX)/man1/coopgammad.1"
-.PHONY: all
-all: bin/coopgammad
-
-.PHONY: base
-base: cmd
-
-.PHONY: cmd
-cmd: bin/coopgammad
-
-bin/coopgammad: $(foreach S,$(SRC),obj/$(S).o)
- @mkdir -p -- "$$(dirname -- "$@")"
- $(CC) $(LDFLAGS) -o $@ $^
-
-obj/%.o: src/%.c src/*.h src/*/*.h
- @mkdir -p -- "$$(dirname -- "$@")"
- $(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $<
-
-
-
-.PHONY: install
-install: install-base install-doc
-
-.PHONY: install-base
-install-base: install-cmd install-copyright
-
-.PHONY: install-copyright
-install-copyright: install-license install-copying
-
-.PHONY: install-doc
-install-doc: install-man
-
-.PHONY: install-cmd
-install-cmd: bin/coopgammad
- mkdir -p -- "$(DESTDIR)$(BINDIR)"
- cp -- bin/coopgammad "$(DESTDIR)$(BINDIR)/$(COMMAND)"
- chmod 0755 -- "$(DESTDIR)$(BINDIR)/$(COMMAND)"
-
-.PHONY: install-license
-install-license:
- mkdir -p -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)"
- cp -- LICENSE "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE"
- chmod 0644 -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE"
-
-.PHONY: install-copying
-install-copying:
- mkdir -p -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)"
- cp -- COPYING "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING"
- chmod 0644 -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING"
-
-.PHONY: install-man
-install-man:
- mkdir -p -- "$(DESTDIR)$(MAN1DIR)"
- cp -- doc/coopgammad.1 "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1"
- chmod 644 -- "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1"
-
-
-
-.PHONY: uninstall
uninstall:
- -rm -- "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1"
- -rm -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING"
- -rm -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE"
- -rmdir -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)"
- -rm -- "$(DESTDIR)$(BINDIR)/$(COMMAND)"
-
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/coopgammad.1"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/coopgammad"
-
-.PHONY: clean
clean:
- -rm -r bin obj
+ -rm -rf -- coopgammad *.o *.su
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+.PHONY: all install uninstall clean
diff --git a/README b/README
index 3954901..c996c26 100644
--- a/README
+++ b/README
@@ -100,4 +100,4 @@ RATIONALE
SEE ALSO
libcoopgamma(7), cg-tools(7), libgamma(7),
- blueshift(1), mds-coopgamma(1).
+ blueshift(1), radharc(1), mds-coopgamma(1).
diff --git a/src/arg.h b/arg.h
index 8418b38..8418b38 100644
--- a/src/arg.h
+++ b/arg.h
diff --git a/communication.c b/communication.c
new file mode 100644
index 0000000..13d5046
--- /dev/null
+++ b/communication.c
@@ -0,0 +1,159 @@
+/* See LICENSE file for copyright and license details. */
+#include "communication.h"
+#include "state.h"
+#include "servers-coopgamma.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <string.h>
+
+
+/**
+ * Send a message
+ *
+ * @param conn The index of the connection
+ * @param buf The data to send
+ * @param n The size of `buf`
+ * @return Zero on success, -1 on error, 1 if disconncted
+ * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count
+ * as success (ECONNRESET cause 1 to be returned),
+ * and are handled appropriately.
+ */
+int
+send_message(size_t conn, char *restrict buf, size_t n)
+{
+ struct ring *restrict ring = outbound + conn;
+ int fd = connections[conn];
+ int saved_errno;
+ size_t ptr = 0;
+ ssize_t sent;
+ size_t chunksize = n;
+ size_t sendsize;
+ size_t old_n;
+ char *old_buf;
+ size_t old_ptr;
+
+ while ((old_buf = ring_peek(ring, &old_n))) {
+ for (old_ptr = 0; old_ptr < n;) {
+ sendsize = old_n - old_ptr < chunksize ? old_n - old_ptr : chunksize;
+ sent = send(fd, old_buf + old_ptr, sendsize, MSG_NOSIGNAL);
+ if (sent < 0) {
+ if (errno == EPIPE)
+ errno = ECONNRESET;
+ if (errno != EMSGSIZE)
+ goto fail;
+ chunksize >>= 1;
+ if (!chunksize)
+ goto fail;
+ continue;
+ }
+ old_ptr += (size_t)sent;
+ ring_pop(ring, (size_t)sent);
+ }
+ }
+
+ while (ptr < n) {
+ sendsize = n - ptr < chunksize ? n - ptr : chunksize;
+ sent = send(fd, buf + ptr, sendsize, MSG_NOSIGNAL);
+ if (sent < 0) {
+ if (errno == EPIPE)
+ errno = ECONNRESET;
+ if (errno != EMSGSIZE)
+ goto fail;
+ chunksize >>= 1;
+ if (!chunksize)
+ goto fail;
+ continue;
+ }
+ ptr += (size_t)sent;
+ }
+
+ free(buf);
+ return 0;
+
+fail:
+ switch (errno) {
+ case EINTR:
+#if defined(EAGAIN)
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ if (ring_push(ring, buf + ptr, n - ptr) < 0)
+ goto proper_fail;
+ free(buf);
+ return 0;
+
+ case ECONNRESET:
+ free(buf);
+ if (connection_closed(fd) < 0)
+ return -1;
+ return 1;
+
+ default:
+ break;
+ }
+
+proper_fail:
+ saved_errno = errno;
+ free(buf);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Send a custom error without an error number
+ *
+ * @param conn The index of the connection
+ * @param message_id The ID of the message to which this message is a response
+ * @param desc The error description to send
+ * @return 1: Client disconnected
+ * 0: Success (possibily delayed)
+ * -1: An error occurred
+ */
+int
+(send_error)(size_t conn, const char *restrict message_id, const char *restrict desc)
+{
+ char *restrict buf;
+ size_t n;
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "Command: error\n"
+ "In response to: %s\n"
+ "Error: custom\n"
+ "Length: %zu\n"
+ "\n"
+ "%s\n",
+ message_id, strlen(desc) + 1, desc);
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Send a standard error
+ *
+ * @param conn The index of the connection
+ * @param message_id The ID of the message to which this message is a response
+ * @param number The value of `errno`, 0 to indicate success
+ * @return 1: Client disconnected
+ * 0: Success (possibily delayed)
+ * -1: An error occurred
+ */
+int
+(send_errno)(size_t conn, const char *restrict message_id, int number)
+{
+ char *restrict buf;
+ size_t n;
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "Command: error\n"
+ "In response to: %s\n"
+ "Error: %i\n"
+ "\n",
+ message_id, number);
+
+ return send_message(conn, buf, n);
+}
diff --git a/src/communication.h b/communication.h
index 244ba9d..b40022b 100644
--- a/src/communication.h
+++ b/communication.h
@@ -1,39 +1,18 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+/* See LICENSE file for copyright and license details. */
#ifndef COMMUNICATION_H
#define COMMUNICATION_H
-
#include <stdio.h>
#include <stdlib.h>
-
-
#ifndef GCC_ONLY
# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
+# define GCC_ONLY(...) __VA_ARGS__
# else
-# define GCC_ONLY(...) /* nothing */
+# define GCC_ONLY(...) /* nothing */
# endif
#endif
-
-
/**
* Construct a message
*
@@ -43,18 +22,16 @@
* @param format:string-literal Message format string
* @param ... Message formatting arguments
*/
-#define MAKE_MESSAGE(bufp, np, extra, format, ...) \
- do \
- { \
- ssize_t m__; \
- snprintf(NULL, 0, format "%zn", __VA_ARGS__, &m__); \
- *(bufp) = malloc((size_t)(extra) + (size_t)m__ + (size_t)1); \
- if (*(bufp) == NULL) \
- return -1; \
- sprintf(*(bufp), format, __VA_ARGS__); \
- *(np) = (size_t)m__; \
- } \
- while (0)
+#define MAKE_MESSAGE(bufp, np, extra, format, ...)\
+ do {\
+ ssize_t m__;\
+ snprintf(NULL, 0, format "%zn", __VA_ARGS__, &m__);\
+ *(bufp) = malloc((size_t)(extra) + (size_t)m__ + (size_t)1);\
+ if (!*(bufp))\
+ return -1;\
+ sprintf(*(bufp), format, __VA_ARGS__);\
+ *(np) = (size_t)m__;\
+ } while (0)
/**
* Send a custom error without an error number
@@ -64,7 +41,7 @@
* 0: Success (possibily delayed)
* -1: An error occurred
*/
-#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__))
+#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__))
/**
* Send a standard error
@@ -74,9 +51,7 @@
* 0: Success (possibily delayed)
* -1: An error occurred
*/
-#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__))
-
-
+#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__))
/**
* Send a message
@@ -89,7 +64,7 @@
* as success (ECONNRESET cause 1 to be returned),
* and are handled appropriately.
*/
-int send_message(size_t conn, char* restrict buf, size_t n);
+int send_message(size_t conn, char *restrict buf, size_t n);
/**
* Send a custom error without an error number
@@ -101,8 +76,8 @@ int send_message(size_t conn, char* restrict buf, size_t n);
* 0: Success (possibily delayed)
* -1: An error occurred
*/
-GCC_ONLY(__attribute__((nonnull)))
-int (send_error)(size_t conn, const char* restrict message_id, const char* restrict desc);
+GCC_ONLY(__attribute__((__nonnull__)))
+int (send_error)(size_t conn, const char *restrict message_id, const char *restrict desc);
/**
* Send a standard error
@@ -114,8 +89,8 @@ int (send_error)(size_t conn, const char* restrict message_id, const char* restr
* 0: Success (possibily delayed)
* -1: An error occurred
*/
-GCC_ONLY(__attribute__((nonnull)))
-int (send_errno)(size_t conn, const char* restrict message_id, int number);
+GCC_ONLY(__attribute__((__nonnull__)))
+int (send_errno)(size_t conn, const char *restrict message_id, int number);
/**
* Continue sending the queued messages
@@ -126,12 +101,10 @@ int (send_errno)(size_t conn, const char* restrict message_id, int number);
* as success (ECONNRESET cause 1 to be returned),
* and are handled appropriately.
*/
-static inline int continue_send(size_t conn)
+static inline int
+continue_send(size_t conn)
{
- return send_message(conn, NULL, 0);
+ return send_message(conn, NULL, 0);
}
-
-
#endif
-
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..2208750
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,12 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+# For class of preserved clut
+PKGNAME = coopgammad
+COMMAND = coopgammad
+
+CPPFLAGS = -D_XOPEN_SOURCE=700 -DUSE_VALGRIND
+#CFLAGS = -std=c99 -Wall -O2
+#LDFLAGS = -lgamma -s
+CFLAGS = -std=c99 -Wall -Og -g
+LDFLAGS = -lgamma
diff --git a/doc/coopgammad.1 b/coopgammad.1
index 58c8ae2..b8166b5 100644
--- a/doc/coopgammad.1
+++ b/coopgammad.1
@@ -117,4 +117,5 @@ implementation-wise.
.BR cg-tools (7),
.BR libgamma (7),
.BR blueshift (1),
+.BR radharc (1),
.BR mds-coopgamma (1).
diff --git a/coopgammad.c b/coopgammad.c
new file mode 100644
index 0000000..50ba53b
--- /dev/null
+++ b/coopgammad.c
@@ -0,0 +1,875 @@
+/* See LICENSE file for copyright and license details. */
+#include "arg.h"
+#include "util.h"
+#include "state.h"
+#include "servers-master.h"
+#include "servers-kernel.h"
+#include "servers-crtc.h"
+#include "servers-gamma.h"
+#include "servers-coopgamma.h"
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/**
+ * Number put in front of the marshalled data
+ * so the program an detect incompatible updates
+ */
+#define MARSHAL_VERSION 0
+
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+
+/**
+ * Lists all function recognised adjustment methods,
+ * will call macro X with the code for the each
+ * adjustment method as the first argument and
+ * corresponding name as the second argument
+ */
+#define LIST_ADJUSTMENT_METHODS\
+ X(LIBGAMMA_METHOD_DUMMY, "dummy")\
+ X(LIBGAMMA_METHOD_X_RANDR, "randr")\
+ X(LIBGAMMA_METHOD_X_VIDMODE, "vidmode")\
+ X(LIBGAMMA_METHOD_LINUX_DRM, "drm")\
+ X(LIBGAMMA_METHOD_W32_GDI, "gdi")\
+ X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz")
+
+
+/**
+ * Used by initialisation functions as their return type. If a
+ * value not listed here is returned by such function, it is the
+ * exit value the process shall exit with as soon as possible.
+ */
+enum init_status {
+ /**
+ * Initialisation was successful
+ */
+ INIT_SUCCESS = -1,
+
+ /**
+ * Initialisation failed
+ */
+ INIT_FAILURE = -2,
+
+ /**
+ * Server is already running
+ */
+ INIT_RUNNING = -3,
+};
+
+
+/**
+ * The pathname of the PID file
+ */
+extern char *restrict pidpath;
+char *restrict pidpath = NULL;
+
+/**
+ * The pathname of the socket
+ */
+extern char *restrict socketpath;
+char *restrict socketpath = NULL;
+
+
+
+/**
+ * Called when the process receives
+ * a signal telling it to re-execute
+ *
+ * @param signo The received signal
+ */
+static void
+sig_reexec(int signo)
+{
+ int saved_errno = errno;
+ reexec = 1;
+ signal(signo, sig_reexec);
+ errno = saved_errno;
+}
+
+
+/**
+ * Called when the process receives
+ * a signal telling it to terminate
+ *
+ * @param signo The received signal
+ */
+static void
+sig_terminate(int signo)
+{
+ terminate = 1;
+ (void) signo;
+}
+
+
+/**
+ * Called when the process receives
+ * a signal telling it to disconnect
+ * from or reconnect to the site
+ *
+ * @param signo The received signal
+ */
+static void
+sig_connection(int signo)
+{
+ int saved_errno = errno;
+ connection = signo - SIGRTMIN + 1;
+ signal(signo, sig_connection);
+ errno = saved_errno;
+}
+
+
+/**
+ * Called when the process receives
+ * a signal telling it to dump its
+ * state to stderr
+ *
+ * @param signo The received signal
+ */
+static void
+sig_info(int signo)
+{
+ int saved_errno = errno;
+ char *env;
+ signal(signo, sig_info);
+ env = getenv("COOPGAMMAD_PIDFILE_TOKEN");
+ fprintf(stderr, "PID file token: %s\n", env ? env : "(null)");
+ fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)");
+ fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)");
+ state_dump();
+ errno = saved_errno;
+}
+
+
+/**
+ * Parse adjustment method name (or stringised number)
+ *
+ * @param arg The adjustment method name (or stringised number)
+ * @return The adjustment method, -1 (negative) on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+get_method(const char *restrict arg)
+{
+#if LIBGAMMA_METHOD_MAX > 5
+# warning libgamma has added more adjustment methods
+#endif
+
+ const char *restrict p;
+
+#define X(C, N) if (!strcmp(arg, N)) return C;
+ LIST_ADJUSTMENT_METHODS;
+#undef X
+
+ if (!*arg || (/* avoid overflow: */ strlen(arg) > 4))
+ goto bad;
+ for (p = arg; *p; p++)
+ if ('0' > *p || *p > '9')
+ goto bad;
+
+ return atoi(arg);
+
+bad:
+ fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg);
+ errno = 0;
+ return -1;
+}
+
+
+/**
+ * Set up signal handlers
+ *
+ * @return Zero on success, -1 on error
+ */
+static int
+set_up_signals(void)
+{
+ if (signal(SIGUSR1, sig_reexec) == SIG_ERR ||
+ signal(SIGUSR2, sig_info) == SIG_ERR ||
+#if defined(SIGINFO)
+ signal(SIGINFO, sig_info) == SIG_ERR ||
+#endif
+ signal(SIGTERM, sig_terminate) == SIG_ERR ||
+ signal(SIGRTMIN + 0, sig_connection) == SIG_ERR ||
+ signal(SIGRTMIN + 1, sig_connection) == SIG_ERR)
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Fork the process to the background
+ *
+ * @param keep_stderr Keep stderr open?
+ * @return An `enum init_status` value or an exit value
+ */
+static enum init_status
+daemonise(int keep_stderr)
+{
+ pid_t pid;
+ int fd = -1, saved_errno;
+ int notify_rw[2] = {-1, -1};
+ char a_byte = 0;
+ ssize_t got;
+
+ if (pipe(notify_rw) < 0)
+ goto fail;
+ if (notify_rw[0] <= STDERR_FILENO)
+ if ((notify_rw[0] = fcntl(notify_rw[0], F_DUPFD, STDERR_FILENO + 1)) < 0)
+ goto fail;
+ if (notify_rw[1] <= STDERR_FILENO)
+ if ((notify_rw[1] = fcntl(notify_rw[1], F_DUPFD, STDERR_FILENO + 1)) < 0)
+ goto fail;
+
+ if ((pid = fork()) < 0)
+ goto fail;
+ if (pid > 0) {
+ /* Original process (parent): */
+ waitpid(pid, NULL, 0);
+ close(notify_rw[1]), notify_rw[1] = -1;
+ got = read(notify_rw[0], &a_byte, 1);
+ if (got < 0)
+ goto fail;
+ close(notify_rw[0]);
+ errno = 0;
+ return got == 0 ? INIT_FAILURE : (enum init_status)0;
+ }
+
+ /* Intermediary process (child): */
+ close(notify_rw[0]), notify_rw[0] = -1;
+ if (setsid() < 0)
+ goto fail;
+ if ((pid = fork()) < 0)
+ goto fail;
+ if (pid > 0) {
+ /* Intermediary process (parent): */
+ return (enum init_status)0;
+ }
+
+ /* Daemon process (child): */
+
+ /* Replace std* with /dev/null */
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0 ||
+ dup2(fd, STDIN_FILENO) < 0 ||
+ dup2(fd, STDOUT_FILENO) < 0 ||
+ (keep_stderr && dup2(fd, STDERR_FILENO) < 0))
+ goto fail;
+ if (fd > STDERR_FILENO)
+ close(fd);
+ fd = -1;
+
+ /* Update PID file */
+ fd = open(pidpath, O_WRONLY);
+ if (fd < 0)
+ goto fail;
+ if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
+ goto fail;
+ close(fd);
+ fd = -1;
+
+ /* Notify */
+ if (write(notify_rw[1], &a_byte, 1) <= 0)
+ goto fail;
+ close(notify_rw[1]);
+
+ return INIT_SUCCESS;
+fail:
+ saved_errno = errno;
+ if (fd >= 0)
+ close(fd);
+ if (notify_rw[0] >= 0)
+ close(notify_rw[0]);
+ if (notify_rw[1] >= 0)
+ close(notify_rw[1]);
+ errno = saved_errno;
+ return INIT_FAILURE;
+}
+
+
+/**
+ * Initialise the process
+ *
+ * @param foreground Keep process in the foreground
+ * @param keep_stderr Keep stderr open
+ * @param query Was -q used, see `main` for description
+ * @return An `enum init_status` value or an exit value
+ */
+static enum init_status
+initialise(int foreground, int keep_stderr, int query)
+{
+ struct rlimit rlimit;
+ size_t i, n;
+ sigset_t mask;
+ int s;
+ enum init_status r;
+
+ /* Zero out some memory so it can be destroyed safely. */
+ memset(&site, 0, sizeof(site));
+
+ if (!query) {
+ /* Close all file descriptors above stderr */
+ if (getrlimit(RLIMIT_NOFILE, &rlimit) || rlimit.rlim_cur == RLIM_INFINITY)
+ n = 4 << 10;
+ else
+ n = (size_t)(rlimit.rlim_cur);
+ for (i = STDERR_FILENO + 1; i < n; i++)
+ close((int)i);
+
+ /* Set umask, reset signal handlers, and reset signal mask */
+ umask(0);
+ for (s = 1; s < _NSIG; s++)
+ signal(s, SIG_DFL);
+ if (sigfillset(&mask))
+ perror(argv0);
+ else
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ }
+
+ /* Get method */
+ if (method < 0 && libgamma_list_methods(&method, 1, 0) < 1)
+ return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1;
+
+ /* Go no further if we are just interested in the adjustment method and site */
+ if (query)
+ return INIT_SUCCESS;
+
+ /* Get site */
+ if (initialise_site() < 0)
+ goto fail;
+
+ /* Get PID file and socket pathname */
+ if (!(pidpath = get_pidfile_pathname()) ||
+ !(socketpath = get_socket_pathname()))
+ goto fail;
+
+ /* Create PID file */
+ if ((r = create_pidfile(pidpath)) < 0) {
+ free(pidpath);
+ pidpath = NULL;
+ if (r == -2)
+ return INIT_RUNNING;
+ goto fail;
+ }
+
+ /* Get partitions and CRTC:s */
+ if (initialise_crtcs() < 0)
+ goto fail;
+
+ /* Get CRTC information */
+ if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs))))
+ goto fail;
+ if (initialise_gamma_info() < 0)
+ goto fail;
+
+ /* Sort outputs */
+ qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name);
+
+ /* Load current gamma ramps */
+ store_gamma();
+
+ /* Preserve current gamma ramps at priority=0 if -p */
+ if (preserve && preserve_gamma() < 0)
+ goto fail;
+
+ /* Create socket and start listening */
+ if (create_socket(socketpath) < 0)
+ goto fail;
+
+ /* Get the real pathname of the process's binary, in case
+ * it is relative, so we can re-execute without problem. */
+ if (*argv0 != '/' && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL)))
+ goto fail;
+
+ /* Change directory to / to avoid blocking umounting */
+ if (chdir("/") < 0)
+ perror(argv0);
+
+ /* Set up signal handlers */
+ if (set_up_signals() < 0)
+ goto fail;
+
+ /* Place in the background unless -f */
+ if (!foreground) {
+ return daemonise(keep_stderr);
+ } else {
+ /* Signal the spawner that the service is ready */
+ close(STDOUT_FILENO);
+ /* Avoid potential catastrophes that would occur if a library
+ * that is being used was so mindless as to write to stdout. */
+ if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0)
+ perror(argv0);
+ }
+
+ return INIT_SUCCESS;
+fail:
+ return INIT_FAILURE;
+}
+
+
+/**
+ * Deinitialise the process
+ *
+ * @param full Perform a full deinitialisation, shall be
+ * done iff the process is going to re-execute
+ */
+static void
+destroy(int full)
+{
+ if (full) {
+ disconnect_all();
+ close_socket(socketpath);
+ free(argv0_real);
+ if (outputs && connected)
+ restore_gamma();
+ }
+ state_destroy();
+ free(socketpath);
+ if (full && pidpath)
+ unlink(pidpath);
+ free(pidpath);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal the state of the process
+ *
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how large the
+ * buffer needs to be
+ * @return The number of marshalled bytes
+ */
+static size_t
+marshal(void *restrict buf)
+{
+ size_t off = 0, n;
+ char *restrict bs = buf;
+
+ if (bs)
+ *(int *)&bs[off] = MARSHAL_VERSION;
+ off += sizeof(int);
+
+ n = strlen(pidpath) + 1;
+ if (bs)
+ memcpy(&bs[off], pidpath, n);
+ off += n;
+
+ n = strlen(socketpath) + 1;
+ if (bs)
+ memcpy(&bs[off], socketpath, n);
+ off += n;
+
+ off += state_marshal(bs ? &bs[off] : NULL);
+
+ return off;
+}
+
+
+/**
+ * Unmarshal the state of the process
+ *
+ * @param buf Buffer with the marshalled data
+ * @return The number of marshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static size_t
+unmarshal(const void *restrict buf)
+{
+ size_t off = 0, n;
+ const char *restrict bs = buf;
+
+ if (*(const int *)&bs[off] != MARSHAL_VERSION) {
+ fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0);
+ errno = 0;
+ return 0;
+ }
+ off += sizeof(int);
+
+ n = strlen(&bs[off]) + 1;
+ if (!(pidpath = memdup(&bs[off], n)))
+ return 0;
+ off += n;
+
+ n = strlen(&bs[off]) + 1;
+ if (!(socketpath = memdup(&bs[off], n)))
+ return 0;
+ off += n;
+
+ off += n = state_unmarshal(&bs[off]);
+ if (!n)
+ return 0;
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+/**
+ * Do minimal initialisation, unmarshal the state of
+ * the process and merge with new state
+ *
+ * @param statefile The state file
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+restore_state(const char *restrict statefile)
+{
+ void *marshalled = NULL;
+ int fd = -1, saved_errno;
+ size_t r, n;
+
+ if (set_up_signals() < 0)
+ goto fail;
+
+ fd = open(statefile, O_RDONLY);
+ if (fd < 0)
+ goto fail;
+
+ if (!(marshalled = nread(fd, &n)))
+ goto fail;
+ close(fd);
+ fd = -1;
+ unlink(statefile);
+ statefile = NULL;
+
+ r = unmarshal(marshalled);
+ if (!r)
+ goto fail;
+ if (r != n) {
+ fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n",
+ argv0, n > r ? "larger" : "smaller", r, n);
+ errno = 0;
+ goto fail;
+ }
+ free(marshalled);
+ marshalled = NULL;
+
+ if (connected) {
+ connected = 0;
+ if (reconnect() < 0)
+ goto fail;
+ }
+
+ return 0;
+fail:
+ saved_errno = errno;
+ if (fd >= 0)
+ close(fd);
+ free(marshalled);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Reexecute the server
+ *
+ * Returns only on failure
+ *
+ * @return Pathname of file where the state is stored,
+ * `NULL` if the state is in tact
+ */
+static char *
+reexecute(void)
+{
+ char *statefile = NULL;
+ char *statebuffer = NULL;
+ size_t buffer_size;
+ int fd = -1, saved_errno;
+
+ statefile = get_state_pathname();
+ if (!statefile)
+ goto fail;
+
+ buffer_size = marshal(NULL);
+ statebuffer = malloc(buffer_size);
+ if (!statebuffer)
+ goto fail;
+ if (marshal(statebuffer) != buffer_size) {
+ fprintf(stderr, "%s: internal error\n", argv0);
+ errno = 0;
+ goto fail;
+ }
+
+ fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
+ if (fd < 0)
+ goto fail;
+
+ if (nwrite(fd, statebuffer, buffer_size) != buffer_size)
+ goto fail;
+ free(statebuffer);
+ statebuffer = NULL;
+
+ if (close(fd) < 0) {
+ fd = -1;
+ if (errno != EINTR)
+ goto fail;
+ }
+ fd = -1;
+
+ destroy(0);
+
+ execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL);
+ free(argv0_real);
+ argv0_real = NULL;
+ return statefile;
+
+fail:
+ saved_errno = errno;
+ free(statebuffer);
+ if (fd >= 0)
+ close(fd);
+ if (statefile != NULL) {
+ unlink(statefile);
+ free(statefile);
+ }
+ errno = saved_errno;
+ return NULL;
+}
+
+
+/**
+ * Print the response for the -q option
+ *
+ * @param query See -q for `main`, must be atleast 1
+ * @return Zero on success, -1 on error
+ */
+static int
+print_method_and_site(int query)
+{
+ const char *restrict methodname = NULL;
+ char *p;
+
+ if (query == 1) {
+ switch (method) {
+#define X(C, N) case C: methodname = N; break;
+ LIST_ADJUSTMENT_METHODS;
+#undef X
+ default:
+ if (printf("%i\n", method) < 0)
+ return -1;
+ break;
+ }
+ if (methodname)
+ if (printf("%s\n", methodname) < 0)
+ return -1;
+ }
+
+ if (!sitename)
+ if ((sitename = libgamma_method_default_site(method)))
+ if (!(sitename = memdup(sitename, strlen(sitename) + 1)))
+ return -1;
+
+ if (sitename) {
+ switch (method) {
+ case LIBGAMMA_METHOD_X_RANDR:
+ case LIBGAMMA_METHOD_X_VIDMODE:
+ if ((p = strrchr(sitename, ':')))
+ if ((p = strchr(p, '.')))
+ *p = '\0';
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (sitename && query == 1)
+ if (printf("%s\n", sitename) < 0)
+ return -1;
+
+ if (query == 2) {
+ site.method = method;
+ site.site = sitename;
+ sitename = NULL;
+ socketpath = get_socket_pathname();
+ if (!socketpath)
+ return -1;
+ if (printf("%s\n", socketpath) < 0)
+ return -1;
+ }
+
+ if (fflush(stdout))
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Print usage information and exit
+ */
+#if defined(__GNU__) || defined(__clang__)
+__attribute__((__noreturn__))
+#endif
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-m method] [-s site] [-fkpq]\n", argv0);
+ exit(1);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command"
+#endif
+
+
+/**
+ * Must not be started without stdin, stdout, or stderr (may be /dev/null)
+ *
+ * argv[0] must refer to the real command name or pathname,
+ * otherwise, re-execute will not work
+ *
+ * The process closes stdout when the socket has been created
+ *
+ * @signal SIGUSR1 Re-execute to updated process image
+ * @signal SIGUSR2 Dump the state of the process to standard error
+ * @signal SIGINFO Ditto
+ * @signal SIGTERM Terminate the process gracefully
+ * @signal SIGRTMIN+0 Disconnect from the site
+ * @signal SiGRTMIN+1 Reconnect to the site
+ *
+ * @param argc The number of elements in `argv`
+ * @param argv Command line arguments. Recognised options:
+ * -s SITENAME
+ * The site to which to connect
+ * -m METHOD
+ * Adjustment method name or adjustment method number
+ * -p
+ * Preserve current gamma ramps at priority 0
+ * -f
+ * Do not fork the process into the background
+ * -k
+ * Keep stderr open
+ * -q
+ * Print the select (possiblity default) adjustment
+ * method on the first line in stdout, and the
+ * selected (possibility defasult) site on the second
+ * line in stdout, and exit. If the site name is `NULL`,
+ * the second line is omitted. This is indented to
+ * be used by clients to figure out to which instance
+ * of the service it should connect. Use twice to
+ * simply ge the socket pathname, an a terminating LF.
+ * By combining -q and -m you can enumerate the name
+ * of all recognised adjustment method, start from 0
+ * and work up until a numerical adjustment method is
+ * returned.
+ * @return 0: Successful
+ * 1: An error occurred
+ * 2: Already running
+ */
+int
+main(int argc, char *argv[])
+{
+ int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r;
+ char *statefile = NULL, *statefile_ = NULL;
+
+ ARGBEGIN {
+ case 's':
+ sitename = EARGF(usage());
+ /* To simplify re-exec: */
+ sitename = memdup(sitename, strlen(sitename) + 1);
+ if (!sitename)
+ goto fail;
+ break;
+ case 'm':
+ method = get_method(EARGF(usage()));
+ if (method < 0)
+ goto fail;
+ break;
+ case 'p': preserve = 1; break;
+ case 'f': foreground = 1; break;
+ case 'k': keep_stderr = 1; break;
+ case 'q': query = 1 + !!query; break;
+ case ' ': /* Internal, do not document */
+ statefile = statefile_ = EARGF(usage());
+ break;
+ default:
+ usage();
+ }
+ ARGEND;
+
+ if (argc > 0)
+ usage();
+
+restart:
+ if (!statefile) {
+ switch ((r = initialise(foreground, keep_stderr, query))) {
+ case INIT_SUCCESS: break;
+ case INIT_RUNNING: rc = 2; /* fall through */
+ case INIT_FAILURE: goto fail;
+ default: return r;
+ }
+ } else if (restore_state(statefile) < 0) {
+ goto fail;
+ } else {
+ if (statefile != statefile_)
+ free(statefile);
+ unlink(statefile);
+ statefile = NULL;
+ }
+
+ if (query) {
+ if (print_method_and_site(query) < 0)
+ goto fail;
+ goto done;
+ }
+
+reenter_loop:
+ if (main_loop() < 0)
+ goto fail;
+
+ if (reexec && !terminate) {
+ statefile = reexecute();
+ if (statefile) {
+ perror(argv0);
+ fprintf(stderr, "%s: restoring state without re-executing\n", argv0);
+ reexec = 0;
+ goto restart;
+ }
+ perror(argv0);
+ fprintf(stderr, "%s: continuing without re-executing\n", argv0);
+ reexec = 0;
+ goto reenter_loop;
+ }
+
+done:
+ rc = 0;
+deinit:
+ if (statefile)
+ unlink(statefile);
+ if (reexec)
+ free(statefile);
+ destroy(1);
+ return rc;
+
+fail:
+ if (errno)
+ perror(argv0);
+ goto deinit;
+}
diff --git a/servers-coopgamma.c b/servers-coopgamma.c
new file mode 100644
index 0000000..f14c0c4
--- /dev/null
+++ b/servers-coopgamma.c
@@ -0,0 +1,524 @@
+/* See LICENSE file for copyright and license details. */
+#include "servers-coopgamma.h"
+#include "servers-gamma.h"
+#include "state.h"
+#include "communication.h"
+#include "util.h"
+#include "types-output.h"
+
+#include <libclut.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * Apply a filter on top of another filter
+ *
+ * @param dest The output for the resulting ramp-trio, must be initialised
+ * @param application The red, green and blue ramps, as one single raw array,
+ * of the filter that should be applied
+ * @param depth -1: `float` stops
+ * -2: `double` stops
+ * Other: the number of bits of each (integral) stop
+ * @param base The CLUT on top of which the new filter should be applied,
+ * this can be the same pointer as `dest`
+ */
+static void
+apply_filter(union gamma_ramps *dest, void *restrict application, int depth, union gamma_ramps *base)
+{
+ union gamma_ramps app;
+ size_t bytedepth;
+ size_t red_width, green_width, blue_width;
+
+ if (depth == -1)
+ bytedepth = sizeof(float);
+ else if (depth == -2)
+ bytedepth = sizeof(double);
+ else
+ bytedepth = (size_t)depth / 8;
+
+ red_width = (app.u8.red_size = base->u8.red_size) * bytedepth;
+ green_width = (app.u8.green_size = base->u8.green_size) * bytedepth;
+ blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth;
+
+ app.u8.red = application;
+ app.u8.green = &app.u8.red[red_width];
+ app.u8.blue = &app.u8.green[green_width];
+
+ if (dest != base) {
+ memcpy(dest->u8.red, base->u8.red, red_width);
+ memcpy(dest->u8.green, base->u8.green, green_width);
+ memcpy(dest->u8.blue, base->u8.blue, blue_width);
+ }
+
+ switch (depth) {
+ case 8:
+ libclut_apply(&dest->u8, UINT8_MAX, uint8_t, &app.u8, UINT8_MAX, uint8_t, 1, 1, 1);
+ break;
+
+ case 16:
+ libclut_apply(&dest->u16, UINT16_MAX, uint16_t, &app.u16, UINT16_MAX, uint16_t, 1, 1, 1);
+ break;
+
+ case 32:
+ libclut_apply(&dest->u32, UINT32_MAX, uint32_t, &app.u32, UINT32_MAX, uint32_t, 1, 1, 1);
+ break;
+
+ case 64:
+ libclut_apply(&dest->u64, UINT64_MAX, uint64_t, &app.u64, UINT64_MAX, uint64_t, 1, 1, 1);
+ break;
+
+ case -1:
+ libclut_apply(&dest->f, 1.0f, float, &app.d, 1.0f, float, 1, 1, 1);
+ break;
+
+ case -2:
+ libclut_apply(&dest->d, (double)1, double, &app.f, (double)1, double, 1, 1, 1);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+
+/**
+ * Remove a filter from an output
+ *
+ * @param out The output
+ * @param filter The filter
+ * @return The index of the filter, `out->table_size` if not found
+ */
+static ssize_t
+remove_filter(struct output *restrict out, struct filter *restrict filter)
+{
+ size_t i, n = out->table_size;
+
+ for (i = 0; i < n; i++)
+ if (!strcmp(filter->class, out->table_filters[i].class))
+ break;
+
+ if (i == out->table_size) {
+ fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n",
+ argv0, out->name, filter->class);
+ return (ssize_t)(out->table_size);
+ }
+
+ filter_destroy(&out->table_filters[i]);
+ libgamma_gamma_ramps8_destroy(&out->table_sums[i].u8);
+
+ n = n - i - 1;
+ memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters)));
+ memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums)));
+ out->table_size--;
+
+ return (ssize_t)i;
+}
+
+
+/**
+ * Add a filter to an output
+ *
+ * @param out The output
+ * @param filter The filter
+ * @return The index given to the filter, -1 on error
+ */
+static ssize_t
+add_filter(struct output *restrict out, struct filter *restrict filter)
+{
+ size_t i, n = out->table_size;
+ int r = -1;
+ void *new;
+
+ /* Remove? */
+ if (filter->lifespan == LIFESPAN_REMOVE)
+ return remove_filter(out, filter);
+
+ /* Update? */
+ for (i = 0; i < n; i++)
+ if (!strcmp(filter->class, out->table_filters[i].class))
+ break;
+ if (i != n) {
+ filter_destroy(&out->table_filters[i]);
+ out->table_filters[i] = *filter;
+ filter->class = NULL;
+ filter->ramps = NULL;
+ return (ssize_t)i;
+ }
+
+ /* Add! */
+ for (i = 0; i < n; i++)
+ if (filter->priority > out->table_filters[i].priority)
+ break;
+
+ if (n == out->table_alloc) {
+ new = realloc(out->table_filters, (n + 10) * sizeof(*out->table_filters));
+ if (!new)
+ return -1;
+ out->table_filters = new;
+
+ new = realloc(out->table_sums, (n + 10) * sizeof(*out->table_sums));
+ if (!new)
+ return -1;
+ out->table_sums = new;
+
+ out->table_alloc += 10;
+ }
+
+ memmove(&out->table_filters[i + 1], &out->table_filters[i], (n - i) * sizeof(*out->table_filters));
+ memmove(&out->table_sums [i + 1], &out->table_sums [i], (n - i) * sizeof(*out->table_sums));
+ out->table_size++;
+
+ out->table_filters[i] = *filter;
+ filter->class = NULL;
+ filter->ramps = NULL;
+
+ COPY_RAMP_SIZES(&out->table_sums[i].u8, out);
+ switch (out->depth) {
+ case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break;
+ case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break;
+ case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break;
+ case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break;
+ case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break;
+ case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break;
+ default:
+ abort();
+ }
+ if (r < 0)
+ return -1;
+
+ return (ssize_t)i;
+}
+
+
+/**
+ * Handle a closed connection
+ *
+ * @param client The file descriptor for the client
+ * @return Zero on success, -1 on error
+ */
+int
+connection_closed(int client)
+{
+ size_t i, j, k;
+ int remove;
+ struct output *output;
+ ssize_t updated;
+
+ for (i = 0; i < outputs_n; i++) {
+ output = outputs + i;
+ updated = -1;
+ for (j = k = 0; j < output->table_size; j += !remove, k++) {
+ if (j != k) {
+ output->table_filters[j] = output->table_filters[k];
+ output->table_sums[j] = output->table_sums[k];
+ }
+ remove = output->table_filters[j].client == client;
+ remove = remove && output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH;
+ if (remove) {
+ filter_destroy(&output->table_filters[j]);
+ libgamma_gamma_ramps8_destroy(&output->table_sums[j].u8);
+ output->table_size -= 1;
+ if (updated == -1)
+ updated = (ssize_t)j;
+ }
+ }
+ if (updated >= 0)
+ if (flush_filters(output, (size_t)updated) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Handle a ‘Command: get-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param coalesce The value of the ‘Coalesce’ header
+ * @param high_priority The value of the ‘High priority’ header
+ * @param low_priority The value of the ‘Low priority’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int
+handle_get_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
+ const char *restrict coalesce, const char *restrict high_priority,
+ const char *restrict low_priority)
+{
+ struct output *restrict output;
+ int64_t high, low;
+ int coal;
+ char *restrict buf;
+ size_t start, end, len, n, i;
+ char depth[3 * sizeof(output->depth) + 2];
+ char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)];
+ union gamma_ramps ramps;
+
+ if (!crtc) return send_error("protocol error: 'CRTC' header omitted");
+ if (!coalesce) return send_error("protocol error: 'Coalesce' header omitted");
+ if (!high_priority) return send_error("protocol error: 'High priority' header omitted");
+ if (!low_priority) return send_error("protocol error: 'Low priority' header omitted");
+
+ high = (int64_t)atoll(high_priority);
+ low = (int64_t)atoll(low_priority);
+
+ if (!strcmp(coalesce, "yes"))
+ coal = 1;
+ else if (!strcmp(coalesce, "no"))
+ coal = 0;
+ else
+ return send_error("protocol error: recognised value for 'Coalesce' header");
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (!output)
+ return send_error("selected CRTC does not exist");
+ else if (output->supported == LIBGAMMA_NO)
+ return send_error("selected CRTC does not support gamma adjustments");
+
+ for (start = 0; start < output->table_size; start++)
+ if (output->table_filters[start].priority <= high)
+ break;
+
+ for (end = output->table_size; end > 0; end--)
+ if (output->table_filters[end - 1].priority >= low)
+ break;
+
+ switch (output->depth) {
+ case -2: strcpy(depth, "d"); break;
+ case -1: strcpy(depth, "f"); break;
+ default:
+ sprintf(depth, "%i", output->depth);
+ break;
+ }
+
+ if (coal) {
+ *tables = '\0';
+ n = output->ramps_size;
+ } else {
+ sprintf(tables, "Tables: %zu\n", end - start);
+ n = (sizeof(int64_t) + output->ramps_size) * (end - start);
+ for (i = start; i < end; i++)
+ n += strlen(output->table_filters[i].class) + 1;
+ }
+
+ MAKE_MESSAGE(&buf, &n, n,
+ "In response to: %s\n"
+ "Depth: %s\n"
+ "Red size: %zu\n"
+ "Green size: %zu\n"
+ "Blue size: %zu\n"
+ "%s"
+ "Length: %zu\n"
+ "\n",
+ message_id, depth, output->red_size, output->green_size,
+ output->blue_size, tables, n);
+
+ if (coal) {
+ if (!start && start < end) {
+ memcpy(&buf[n], output->table_sums[end - 1].u8.red, output->ramps_size);
+ } else {
+ if (make_plain_ramps(&ramps, output)) {
+ free(buf);
+ return -1;
+ }
+ for (i = start; i < end; i++)
+ apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps);
+ memcpy(&buf[n], ramps.u8.red, output->ramps_size);
+ libgamma_gamma_ramps8_destroy(&(ramps.u8));
+ }
+ n += output->ramps_size;
+ } else {
+ for (i = start; i < end; i++) {
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+ *(int64_t *)&buf[n] = output->table_filters[i].priority;
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+ n += sizeof(int64_t);
+ len = strlen(output->table_filters[i].class) + 1;
+ memcpy(&buf[n], output->table_filters[i].class, len);
+ n += len;
+ memcpy(&buf[n], output->table_filters[i].ramps, output->ramps_size);
+ n += output->ramps_size;
+ }
+ }
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @param priority The value of the ‘Priority’ header
+ * @param class The value of the ‘Class’ header
+ * @param lifespan The value of the ‘Lifespan’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int
+handle_set_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
+ const char *restrict priority, const char *restrict class, const char *restrict lifespan)
+{
+ struct message *restrict msg = inbound + conn;
+ struct output *restrict output = NULL;
+ struct filter filter;
+ char *restrict p;
+ char *restrict q;
+ int saved_errno;
+ ssize_t r;
+
+ if (!crtc) return send_error("protocol error: 'CRTC' header omitted");
+ if (!class) return send_error("protocol error: 'Class' header omitted");
+ if (!lifespan) return send_error("protocol error: 'Lifespan' header omitted");
+
+ filter.client = connections[conn];
+ filter.priority = !priority ? 0 : (int64_t)atoll(priority);
+ filter.ramps = NULL;
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (!output)
+ return send_error("CRTC does not exists");
+
+ p = strstr(class, "::");
+ if (!p || p == class)
+ return send_error("protocol error: malformatted value for 'Class' header");
+ q = strstr(p + 2, "::");
+ if (!q || q == p)
+ return send_error("protocol error: malformatted value for 'Class' header");
+
+ if (!strcmp(lifespan, "until-removal"))
+ filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
+ else if (!strcmp(lifespan, "until-death"))
+ filter.lifespan = LIFESPAN_UNTIL_DEATH;
+ else if (!strcmp(lifespan, "remove"))
+ filter.lifespan = LIFESPAN_REMOVE;
+ else
+ return send_error("protocol error: recognised value for 'Lifespan' header");
+
+ if (filter.lifespan == LIFESPAN_REMOVE) {
+ if (msg->payload_size)
+ fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with "
+ "Lifespan: remove\n", argv0);
+ if (priority)
+ fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with "
+ "Lifespan: remove\n", argv0);
+ } else if (msg->payload_size != output->ramps_size) {
+ return send_error("invalid payload: size of message payload does matched the expectancy");
+ } else if (!priority) {
+ return send_error("protocol error: 'Priority' header omitted");
+ }
+
+ filter.class = memdup(class, strlen(class) + 1);
+ if (!filter.class)
+ goto fail;
+
+ if (filter.lifespan != LIFESPAN_REMOVE) {
+ filter.ramps = memdup(msg->payload, msg->payload_size);
+ if (!filter.ramps)
+ goto fail;
+ }
+
+ if ((r = add_filter(output, &filter)) < 0)
+ goto fail;
+ if (flush_filters(output, (size_t)r))
+ goto fail;
+
+ free(filter.class);
+ free(filter.ramps);
+ return send_errno(0);
+
+fail:
+ saved_errno = errno;
+ send_errno(saved_errno);
+ free(filter.class);
+ free(filter.ramps);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Recalculate the resulting gamma and
+ * update push the new gamma ramps to the CRTC
+ *
+ * @param output The output
+ * @param first_updated The index of the first added or removed filter
+ * @return Zero on success, -1 on error
+ */
+int
+flush_filters(struct output *restrict output, size_t first_updated)
+{
+ union gamma_ramps plain, *last;
+ size_t i;
+
+ if (!first_updated) {
+ if (make_plain_ramps(&plain, output) < 0)
+ return -1;
+ last = &plain;
+ } else {
+ last = output->table_sums + (first_updated - 1);
+ }
+
+ for (i = first_updated; i < output->table_size; i++) {
+ apply_filter(&output->table_sums[i], output->table_filters[i].ramps, output->depth, last);
+ last = output->table_sums + i;
+ }
+
+ set_gamma(output, last);
+
+ if (!first_updated)
+ libgamma_gamma_ramps8_destroy(&plain.u8);
+
+ return 0;
+}
+
+
+/**
+ * Preserve current gamma ramps at priority 0 for all outputs
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+preserve_gamma(void)
+{
+ size_t i;
+ struct filter filter;
+
+ for (i = 0; i < outputs_n; i++) {
+ filter.client = -1;
+ filter.priority = 0;
+ filter.class = NULL;
+ filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
+ filter.ramps = NULL;
+ outputs[i].table_filters = calloc(4, sizeof(*outputs[i].table_filters));
+ outputs[i].table_sums = calloc(4, sizeof(*outputs[i].table_sums));
+ outputs[i].table_alloc = 4;
+ outputs[i].table_size = 1;
+ filter.class = memdup(PKGNAME"::"COMMAND"::preserved", sizeof(PKGNAME"::"COMMAND"::preserved"));
+ if (!filter.class)
+ return -1;
+ filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size);
+ if (!filter.ramps)
+ return -1;
+ outputs[i].table_filters[0] = filter;
+ COPY_RAMP_SIZES(&outputs[i].table_sums[0].u8, outputs + i);
+ if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size))
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/servers/coopgamma.h b/servers-coopgamma.h
index 5a9f7d9..584e760 100644
--- a/src/servers/coopgamma.h
+++ b/servers-coopgamma.h
@@ -1,40 +1,19 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+/* See LICENSE file for copyright and license details. */
#ifndef SERVERS_COOPGAMMA_H
#define SERVERS_COOPGAMMA_H
-
-#include "../types/output.h"
+#include "types-output.h"
#include <stddef.h>
-
-
#ifndef GCC_ONLY
# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
+# define GCC_ONLY(...) __VA_ARGS__
# else
-# define GCC_ONLY(...) /* nothing */
+# define GCC_ONLY(...) /* nothing */
# endif
#endif
-
-
/**
* Handle a closed connection
*
@@ -55,10 +34,10 @@ int connection_closed(int client);
* @return Zero on success (even if ignored), -1 on error,
* 1 if connection closed
*/
-GCC_ONLY(__attribute__((nonnull(2))))
-int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
- const char* restrict coalesce, const char* restrict high_priority,
- const char* restrict low_priority);
+GCC_ONLY(__attribute__((__nonnull__(2))))
+int handle_get_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
+ const char *restrict coalesce, const char *restrict high_priority,
+ const char *restrict low_priority);
/**
* Handle a ‘Command: set-gamma’ message
@@ -72,10 +51,9 @@ int handle_get_gamma(size_t conn, const char* restrict message_id, const char* r
* @return Zero on success (even if ignored), -1 on error,
* 1 if connection closed
*/
-GCC_ONLY(__attribute__((nonnull(2))))
-int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
- const char* restrict priority, const char* restrict class, const char* restrict lifespan);
-
+GCC_ONLY(__attribute__((__nonnull__(2))))
+int handle_set_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc,
+ const char *restrict priority, const char *restrict class, const char *restrict lifespan);
/**
* Recalculate the resulting gamma and
@@ -85,9 +63,8 @@ int handle_set_gamma(size_t conn, const char* restrict message_id, const char* r
* @param first_updated The index of the first added or removed filter
* @return Zero on success, -1 on error
*/
-GCC_ONLY(__attribute__((nonnull)))
-int flush_filters(struct output* restrict output, size_t first_updated);
-
+GCC_ONLY(__attribute__((__nonnull__)))
+int flush_filters(struct output *restrict output, size_t first_updated);
/**
* Preserve current gamma ramps at priority 0 for all outputs
@@ -96,6 +73,4 @@ int flush_filters(struct output* restrict output, size_t first_updated);
*/
int preserve_gamma(void);
-
#endif
-
diff --git a/servers-crtc.c b/servers-crtc.c
new file mode 100644
index 0000000..24018e1
--- /dev/null
+++ b/servers-crtc.c
@@ -0,0 +1,312 @@
+/* See LICENSE file for copyright and license details. */
+#include "servers-crtc.h"
+#include "servers-gamma.h"
+#include "servers-coopgamma.h"
+#include "state.h"
+#include "communication.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+/**
+ * Handle a ‘Command: enumerate-crtcs’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int
+handle_enumerate_crtcs(size_t conn, const char *restrict message_id)
+{
+ size_t i, n = 0, len;
+ char *restrict buf;
+
+ for (i = 0; i < outputs_n; i++)
+ n += strlen(outputs[i].name) + 1;
+
+ MAKE_MESSAGE(&buf, &n, n,
+ "Command: crtc-enumeration\n"
+ "In response to: %s\n"
+ "Length: %zu\n"
+ "\n",
+ message_id, n);
+
+ for (i = 0; i < outputs_n; i++) {
+ len = strlen(outputs[i].name);
+ memcpy(&buf[n], outputs[i].name, len);
+ buf[n + len] = '\n';
+ n += len + 1;
+ }
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Get the name of a CRTC
+ *
+ * @param info Information about the CRTC
+ * @param crtc libgamma's state for the CRTC
+ * @return The name of the CRTC, `NULL` on error
+ */
+char *
+get_crtc_name(const libgamma_crtc_information_t *restrict info, const libgamma_crtc_state_t *restrict crtc)
+{
+ char *name;
+ if (!info->edid_error && info->edid) {
+ return libgamma_behex_edid(info->edid, info->edid_length);
+ } else if (!info->connector_name_error && info->connector_name) {
+ name = malloc(3 * sizeof(size_t) + strlen(info->connector_name) + 2);
+ if (name)
+ sprintf(name, "%zu.%s", crtc->partition->partition, info->connector_name);
+ return name;
+ } else {
+ name = malloc(2 * 3 * sizeof(size_t) + 2);
+ if (name)
+ sprintf(name, "%zu.%zu", crtc->partition->partition, crtc->crtc);
+ return name;
+ }
+}
+
+
+/**
+ * Initialise the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+initialise_site(void)
+{
+ char *restrict sitename_dup = NULL;
+ int gerror;
+
+ if (sitename && !(sitename_dup = memdup(sitename, strlen(sitename) + 1)))
+ goto fail;
+ if ((gerror = libgamma_site_initialise(&site, method, sitename_dup)))
+ goto fail_libgamma;
+
+ return 0;
+
+fail_libgamma:
+ sitename_dup = NULL;
+ libgamma_perror(argv0, gerror);
+ errno = 0;
+fail:
+ free(sitename_dup);
+ return -1;
+}
+
+
+/**
+ * Get partitions and CRTC:s
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+initialise_crtcs(void)
+{
+ size_t i, j, n, n0;
+ int gerror;
+
+ /* Get partitions */
+ outputs_n = 0;
+ if (site.partitions_available) {
+ partitions = calloc(site.partitions_available, sizeof(*partitions));
+ if (!partitions)
+ goto fail;
+ }
+ for (i = 0; i < site.partitions_available; i++) {
+ if ((gerror = libgamma_partition_initialise(&partitions[i], &site, i)))
+ goto fail_libgamma;
+ outputs_n += partitions[i].crtcs_available;
+ }
+
+ /* Get CRTC:s */
+ if (outputs_n) {
+ crtcs = calloc(outputs_n, sizeof(*crtcs));
+ if (!crtcs)
+ goto fail;
+ }
+ for (i = 0, j = n = 0; i < site.partitions_available; i++)
+ for (n0 = n, n += partitions[i].crtcs_available; j < n; j++)
+ if ((gerror = libgamma_crtc_initialise(&crtcs[j], &partitions[i], j - n0)))
+ goto fail_libgamma;
+
+ return 0;
+
+fail_libgamma:
+ libgamma_perror(argv0, gerror);
+ errno = 0;
+fail:
+ return -1;
+}
+
+
+/**
+ * Merge the new state with an old state
+ *
+ * @param old_outputs The old `outputs`
+ * @param old_outputs_n The old `outputs_n`
+ * @return Zero on success, -1 on error
+ */
+int
+merge_state(struct output *restrict old_outputs, size_t old_outputs_n)
+{
+ struct output *restrict new_outputs = NULL;
+ size_t new_outputs_n;
+ size_t i, j;
+ int cmp, is_same;
+
+ /* How many outputs does the system now have? */
+ i = j = new_outputs_n = 0;
+ while (i < old_outputs_n && j < outputs_n) {
+ cmp = strcmp(old_outputs[i].name, outputs[j].name);
+ if (cmp <= 0)
+ new_outputs_n++;
+ i += cmp >= 0;
+ j += cmp <= 0;
+ }
+ new_outputs_n += outputs_n - j;
+
+ /* Allocate output state array */
+ if (new_outputs_n > 0) {
+ new_outputs = calloc(new_outputs_n, sizeof(*new_outputs));
+ if (!new_outputs)
+ return -1;
+ }
+
+ /* Merge output states */
+ i = j = new_outputs_n = 0;
+ while (i < old_outputs_n && j < outputs_n) {
+ is_same = 0;
+ cmp = strcmp(old_outputs[i].name, outputs[j].name);
+ if (!cmp) {
+ is_same = (old_outputs[i].depth == outputs[j].depth &&
+ old_outputs[i].red_size == outputs[j].red_size &&
+ old_outputs[i].green_size == outputs[j].green_size &&
+ old_outputs[i].blue_size == outputs[j].blue_size);
+ }
+ if (is_same) {
+ new_outputs[new_outputs_n] = old_outputs[i];
+ new_outputs[new_outputs_n].crtc = outputs[j].crtc;
+ memset(&old_outputs[i], 0, sizeof(*old_outputs));
+ outputs[j].crtc = NULL;
+ output_destroy(&outputs[j]);
+ new_outputs_n++;
+ } else if (cmp <= 0) {
+ new_outputs[new_outputs_n++] = outputs[j];
+ }
+ i += cmp >= 0;
+ j += cmp <= 0;
+ }
+ while (j < outputs_n)
+ new_outputs[new_outputs_n++] = outputs[j++];
+
+ /* Commit merge */
+ free(outputs);
+ outputs = new_outputs;
+ outputs_n = new_outputs_n;
+
+ return 0;
+}
+
+
+/**
+ * Disconnect from the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+disconnect(void)
+{
+ size_t i;
+
+ if (!connected)
+ return 0;
+ connected = 0;
+
+ for (i = 0; i < outputs_n; i++) {
+ outputs[i].crtc = NULL;
+ libgamma_crtc_destroy(&crtcs[i]);
+ }
+ free(crtcs);
+ crtcs = NULL;
+
+ for (i = 0; i < site.partitions_available; i++)
+ libgamma_partition_destroy(&partitions[i]);
+ free(partitions);
+ partitions = NULL;
+
+ libgamma_site_destroy(&site);
+ memset(&site, 0, sizeof(site));
+
+ return 0;
+}
+
+
+/**
+ * Reconnect to the site
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+reconnect(void)
+{
+ struct output *restrict old_outputs;
+ size_t i, old_outputs_n;
+
+ if (connected)
+ return 0;
+ connected = 1;
+
+ /* Remember old state */
+ old_outputs = outputs, outputs = NULL;
+ old_outputs_n = outputs_n, outputs_n = 0;
+
+ /* Get site */
+ if (initialise_site() < 0)
+ goto fail;
+
+ /* Get partitions and CRTC:s */
+ if (initialise_crtcs() < 0)
+ goto fail;
+
+ /* Get CRTC information */
+ if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs))))
+ goto fail;
+ if (initialise_gamma_info() < 0)
+ goto fail;
+
+ /* Sort outputs */
+ qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name);
+
+ /* Load current gamma ramps */
+ store_gamma();
+
+ /* Preserve current gamma ramps at priority=0 if -p */
+ if (preserve && preserve_gamma() < 0)
+ goto fail;
+
+ /* Merge state */
+ if (merge_state(old_outputs, old_outputs_n) < 0)
+ goto fail;
+ for (i = 0; i < old_outputs_n; i++)
+ output_destroy(old_outputs + i);
+ free(old_outputs);
+ old_outputs = NULL;
+ old_outputs_n = 0;
+
+ /* Reapply gamma ramps */
+ reapply_gamma();
+
+ return 0;
+
+fail:
+ for (i = 0; i < old_outputs_n; i++)
+ output_destroy(&old_outputs[i]);
+ free(old_outputs);
+ return -1;
+}
diff --git a/src/servers/crtc.h b/servers-crtc.h
index 68239d4..08e4b02 100644
--- a/src/servers/crtc.h
+++ b/servers-crtc.h
@@ -1,40 +1,19 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+/* See LICENSE file for copyright and license details. */
#ifndef SERVERS_CRTC_H
#define SERVERS_CRTC_H
-
-#include "../types/output.h"
+#include "types-output.h"
#include <libgamma.h>
-
-
#ifndef GCC_ONLY
# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
+# define GCC_ONLY(...) __VA_ARGS__
# else
-# define GCC_ONLY(...) /* nothing */
+# define GCC_ONLY(...) /* nothing */
# endif
#endif
-
-
/**
* Handle a ‘Command: enumerate-crtcs’ message
*
@@ -43,8 +22,8 @@
* @return Zero on success (even if ignored), -1 on error,
* 1 if connection closed
*/
-GCC_ONLY(__attribute__((nonnull)))
-int handle_enumerate_crtcs(size_t conn, const char* restrict message_id);
+GCC_ONLY(__attribute__((__nonnull__)))
+int handle_enumerate_crtcs(size_t conn, const char *restrict message_id);
/**
* Get the name of a CRTC
@@ -53,9 +32,8 @@ int handle_enumerate_crtcs(size_t conn, const char* restrict message_id);
* @param crtc libgamma's state for the CRTC
* @return The name of the CRTC, `NULL` on error
*/
-GCC_ONLY(__attribute__((nonnull)))
-char* get_crtc_name(const libgamma_crtc_information_t* restrict info,
- const libgamma_crtc_state_t* restrict crtc);
+GCC_ONLY(__attribute__((__nonnull__)))
+char *get_crtc_name(const libgamma_crtc_information_t *restrict info, const libgamma_crtc_state_t *restrict crtc);
/**
* Initialise the site
@@ -78,8 +56,7 @@ int initialise_crtcs(void);
* @param old_outputs_n The old `outputs_n`
* @return Zero on success, -1 on error
*/
-int merge_state(struct output* restrict old_outputs, size_t old_outputs_n);
-
+int merge_state(struct output *restrict old_outputs, size_t old_outputs_n);
/**
* Disconnect from the site
@@ -95,6 +72,4 @@ int disconnect(void);
*/
int reconnect(void);
-
#endif
-
diff --git a/servers-gamma.c b/servers-gamma.c
new file mode 100644
index 0000000..8907e8f
--- /dev/null
+++ b/servers-gamma.c
@@ -0,0 +1,381 @@
+/* See LICENSE file for copyright and license details. */
+#include "servers-gamma.h"
+#include "servers-crtc.h"
+#include "state.h"
+#include "communication.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wswitch-enum"
+#endif
+
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+int
+handle_get_gamma_info(size_t conn, const char *restrict message_id, const char *restrict crtc)
+{
+ struct output *restrict output;
+ char *restrict buf;
+ char depth[3 * sizeof(output->depth) + 2];
+ const char *supported;
+ const char *colourspace;
+ char gamut[8 * (sizeof("White x: ") + 3 * sizeof(unsigned))];
+ size_t n;
+
+ if (!crtc)
+ return send_error("protocol error: 'CRTC' header omitted");
+
+ output = output_find_by_name(crtc, outputs, outputs_n);
+ if (!output)
+ return send_error("selected CRTC does not exist");
+
+ switch (output->depth) {
+ case -2: strcpy(depth, "d"); break;
+ case -1: strcpy(depth, "f"); break;
+ default:
+ sprintf(depth, "%i", output->depth);
+ break;
+ }
+
+ switch (output->supported) {
+ case LIBGAMMA_YES: supported = "yes"; break;
+ case LIBGAMMA_NO: supported = "no"; break;
+ case LIBGAMMA_MAYBE:
+ default: supported = "maybe"; break;
+ }
+
+ switch (output->colourspace) {
+ case COLOURSPACE_SRGB_SANS_GAMUT:
+ case COLOURSPACE_SRGB: colourspace = "Colour space: sRGB\n"; break;
+ case COLOURSPACE_RGB_SANS_GAMUT:
+ case COLOURSPACE_RGB: colourspace = "Colour space: RGB\n"; break;
+ case COLOURSPACE_NON_RGB: colourspace = "Colour space: non-RGB\n"; break;
+ case COLOURSPACE_GREY: colourspace = "Colour space: grey\n"; break;
+ case COLOURSPACE_UNKNOWN:
+ default: colourspace = ""; break;
+ }
+
+ switch (output->colourspace) {
+ case COLOURSPACE_SRGB:
+ case COLOURSPACE_RGB:
+ sprintf(gamut,
+ "Red x: %u\n" "Red y: %u\n"
+ "Green x: %u\n" "Green y: %u\n"
+ "Blue x: %u\n" "Blue y: %u\n"
+ "White x: %u\n" "White y: %u\n",
+ output->red_x, output->red_y, output->green_x, output->green_y,
+ output->blue_x, output->blue_y, output->white_x, output->white_y);
+ break;
+ case COLOURSPACE_SRGB_SANS_GAMUT:
+ case COLOURSPACE_RGB_SANS_GAMUT:
+ case COLOURSPACE_NON_RGB:
+ case COLOURSPACE_GREY:
+ case COLOURSPACE_UNKNOWN:
+ default:
+ *gamut = '\0';
+ break;
+ }
+
+ MAKE_MESSAGE(&buf, &n, 0,
+ "In response to: %s\n"
+ "Cooperative: yes\n" /* In mds: say ‘no’, mds-coopgamma changes to ‘yes’.” */
+ "Depth: %s\n"
+ "Red size: %zu\n"
+ "Green size: %zu\n"
+ "Blue size: %zu\n"
+ "Gamma support: %s\n"
+ "%s%s"
+ "\n",
+ message_id, depth, output->red_size, output->green_size,
+ output->blue_size, supported, gamut, colourspace);
+
+ return send_message(conn, buf, n);
+}
+
+
+/**
+ * Set the gamma ramps on an output
+ *
+ * @param output The output
+ * @param ramps The gamma ramps
+ */
+void
+set_gamma(const struct output *restrict output, const union gamma_ramps *restrict ramps)
+{
+ int r = 0;
+
+ if (!connected)
+ return;
+
+ switch (output->depth) {
+ case 8: r = libgamma_crtc_set_gamma_ramps8(output->crtc, ramps->u8); break;
+ case 16: r = libgamma_crtc_set_gamma_ramps16(output->crtc, ramps->u16); break;
+ case 32: r = libgamma_crtc_set_gamma_ramps32(output->crtc, ramps->u32); break;
+ case 64: r = libgamma_crtc_set_gamma_ramps64(output->crtc, ramps->u64); break;
+ case -1: r = libgamma_crtc_set_gamma_rampsf(output->crtc, ramps->f); break;
+ case -2: r = libgamma_crtc_set_gamma_rampsd(output->crtc, ramps->d); break;
+ default:
+ abort();
+ }
+
+ if (r)
+ libgamma_perror(argv0, r); /* Not fatal */
+}
+
+
+/**
+ * Parse the EDID of a monitor
+ *
+ * @param output The output
+ * @param edid The EDID in binary format
+ * @param n The length of the EDID
+ */
+static void
+parse_edid(struct output *restrict output, const unsigned char *restrict edid, size_t n)
+{
+ size_t i;
+ int analogue;
+ unsigned sum;
+
+ output->red_x = output->green_x = output->blue_x = output->white_x = 0;
+ output->red_y = output->green_y = output->blue_y = output->white_y = 0;
+ output->colourspace = COLOURSPACE_UNKNOWN;
+
+ if (!edid || n < 128)
+ return;
+ for (i = 0, sum = 0; i < 128; i++)
+ sum += (unsigned)edid[i];
+ if ((sum & 0xFF) != 0)
+ return;
+ if (edid[0] || edid[7])
+ return;
+ for (i = 1; i < 7; i++)
+ if (edid[i] != 0xFF)
+ return;
+
+ analogue = !(edid[20] & 0x80);
+ if (!analogue) {
+ output->colourspace = COLOURSPACE_RGB;
+ } else {
+ switch ((edid[24] >> 3) & 3) {
+ case 0: output->colourspace = COLOURSPACE_GREY; break;
+ case 1: output->colourspace = COLOURSPACE_RGB; break;
+ case 2: output->colourspace = COLOURSPACE_NON_RGB; break;
+ default: output->colourspace = COLOURSPACE_UNKNOWN; break;
+ }
+ }
+
+ if (output->colourspace != COLOURSPACE_RGB)
+ return;
+
+ if (edid[24] & 4)
+ output->colourspace = COLOURSPACE_SRGB;
+
+ output->red_x = (edid[25] >> 6) & 3;
+ output->red_y = (edid[25] >> 4) & 3;
+ output->green_x = (edid[25] >> 2) & 3;
+ output->green_y = (edid[25] >> 0) & 3;
+ output->blue_x = (edid[26] >> 6) & 3;
+ output->blue_y = (edid[26] >> 4) & 3;
+ output->white_x = (edid[26] >> 2) & 3;
+ output->white_y = (edid[26] >> 0) & 3;
+
+ output->red_x |= ((unsigned)(edid[27])) << 2;
+ output->red_y |= ((unsigned)(edid[28])) << 2;
+ output->green_x |= ((unsigned)(edid[29])) << 2;
+ output->green_y |= ((unsigned)(edid[30])) << 2;
+ output->blue_x |= ((unsigned)(edid[31])) << 2;
+ output->blue_y |= ((unsigned)(edid[32])) << 2;
+ output->white_x |= ((unsigned)(edid[33])) << 2;
+ output->white_y |= ((unsigned)(edid[34])) << 2;
+
+ if ((output->red_x == output->red_y) &&
+ (output->red_x == output->green_x) &&
+ (output->red_x == output->green_y) &&
+ (output->red_x == output->blue_x) &&
+ (output->red_x == output->blue_y) &&
+ (output->red_x == output->white_x) &&
+ (output->red_x == output->white_y)) {
+ if (output->colourspace == COLOURSPACE_SRGB)
+ output->colourspace = COLOURSPACE_SRGB_SANS_GAMUT;
+ else
+ output->colourspace = COLOURSPACE_RGB_SANS_GAMUT;
+ }
+}
+
+
+/**
+ * Store all current gamma ramps
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+initialise_gamma_info(void)
+{
+ libgamma_crtc_information_t info;
+ int saved_errno;
+ size_t i;
+
+ for (i = 0; i < outputs_n; i++) {
+ libgamma_get_crtc_information(&info, crtcs + i,
+ LIBGAMMA_CRTC_INFO_EDID |
+ LIBGAMMA_CRTC_INFO_MACRO_RAMP |
+ LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT |
+ LIBGAMMA_CRTC_INFO_CONNECTOR_NAME);
+
+ outputs[i].depth = info.gamma_depth_error ? 0 : info.gamma_depth;
+ outputs[i].red_size = info.gamma_size_error ? 0 : info.red_gamma_size;
+ outputs[i].green_size = info.gamma_size_error ? 0 : info.green_gamma_size;
+ outputs[i].blue_size = info.gamma_size_error ? 0 : info.blue_gamma_size;
+ outputs[i].supported = info.gamma_support_error ? 0 : info.gamma_support;
+
+ if (info.gamma_support_error == LIBGAMMA_CRTC_INFO_NOT_SUPPORTED)
+ outputs[i].supported = LIBGAMMA_MAYBE;
+
+ if (!outputs[i].depth || !outputs[i].red_size ||
+ !outputs[i].green_size || !outputs[i].blue_size)
+ outputs[i].supported = 0;
+
+ parse_edid(&outputs[i], info.edid_error ? NULL : info.edid, info.edid_error ? 0 : info.edid_length);
+
+ outputs[i].name = get_crtc_name(&info, crtcs + i);
+
+ saved_errno = errno;
+ outputs[i].name_is_edid = (!info.edid_error && info.edid);
+ outputs[i].crtc = &crtcs[i];
+
+ libgamma_crtc_information_destroy(&info);
+ outputs[i].ramps_size = outputs[i].red_size + outputs[i].green_size + outputs[i].blue_size;
+
+ switch (outputs[i].depth) {
+ default:
+ outputs[i].depth = 64;
+ /* Fall through */
+ case 8:
+ case 16:
+ case 32:
+ case 64: outputs[i].ramps_size *= (size_t)(outputs[i].depth / 8); break;
+ case -2: outputs[i].ramps_size *= sizeof(double); break;
+ case -1: outputs[i].ramps_size *= sizeof(float); break;
+ }
+
+ errno = saved_errno;
+ if (!outputs[i].name)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Store all current gamma ramps
+ */
+void
+store_gamma(void)
+{
+ int gerror;
+ size_t i;
+
+#define LOAD_RAMPS(SUFFIX, MEMBER)\
+ do {\
+ libgamma_gamma_ramps##SUFFIX##_initialise(&outputs[i].saved_ramps.MEMBER);\
+ gerror = libgamma_crtc_get_gamma_ramps##SUFFIX(outputs[i].crtc, &outputs[i].saved_ramps.MEMBER);\
+ if (gerror) {\
+ libgamma_perror(argv0, gerror);\
+ outputs[i].supported = LIBGAMMA_NO;\
+ libgamma_gamma_ramps##SUFFIX##_destroy(&outputs[i].saved_ramps.MEMBER);\
+ memset(&outputs[i].saved_ramps.MEMBER, 0, sizeof(outputs[i].saved_ramps.MEMBER));\
+ }\
+ } while (0)
+
+ for (i = 0; i < outputs_n; i++) {
+ if (outputs[i].supported == LIBGAMMA_NO)
+ continue;
+
+ outputs[i].saved_ramps.u8.red_size = outputs[i].red_size;
+ outputs[i].saved_ramps.u8.green_size = outputs[i].green_size;
+ outputs[i].saved_ramps.u8.blue_size = outputs[i].blue_size;
+
+ switch (outputs[i].depth) {
+ case 64: LOAD_RAMPS(64, u64); break;
+ case 32: LOAD_RAMPS(32, u32); break;
+ case 16: LOAD_RAMPS(16, u16); break;
+ case 8: LOAD_RAMPS( 8, u8); break;
+ case -2: LOAD_RAMPS(d, d); break;
+ case -1: LOAD_RAMPS(f, f); break;
+ default: /* impossible */ break;
+ }
+ }
+}
+
+
+/**
+ * Restore all gamma ramps
+ */
+void
+restore_gamma(void)
+{
+ size_t i;
+ int gerror;
+
+#define RESTORE_RAMPS(SUFFIX, MEMBER)\
+ do {\
+ if (outputs[i].saved_ramps.MEMBER.red) {\
+ gerror = libgamma_crtc_set_gamma_ramps##SUFFIX(outputs[i].crtc, outputs[i].saved_ramps.MEMBER);\
+ if (gerror)\
+ libgamma_perror(argv0, gerror);\
+ }\
+ } while (0)
+
+ for (i = 0; i < outputs_n; i++) {
+ if (outputs[i].supported == LIBGAMMA_NO)
+ continue;
+ if (!outputs[i].saved_ramps.u8.red)
+ continue;
+
+ switch (outputs[i].depth){
+ case 64: RESTORE_RAMPS(64, u64); break;
+ case 32: RESTORE_RAMPS(32, u32); break;
+ case 16: RESTORE_RAMPS(16, u16); break;
+ case 8: RESTORE_RAMPS( 8, u8); break;
+ case -2: RESTORE_RAMPS(d, d); break;
+ case -1: RESTORE_RAMPS(f, f); break;
+ default: /* impossible */ break;
+ }
+ }
+}
+
+
+/**
+ * Reapplu all gamma ramps
+ */
+void
+reapply_gamma(void)
+{
+ union gamma_ramps plain;
+ size_t i;
+
+ /* Reapply gamma ramps */
+ for (i = 0; i < outputs_n; i++) {
+ if (outputs[i].table_size > 0) {
+ set_gamma(&outputs[i], &outputs[i].table_sums[outputs[i].table_size - 1]);
+ } else {
+ make_plain_ramps(&plain, &outputs[i]);
+ set_gamma(&outputs[i], &plain);
+ libgamma_gamma_ramps8_destroy(&plain.u8);
+ }
+ }
+}
diff --git a/servers-gamma.h b/servers-gamma.h
new file mode 100644
index 0000000..e522cf5
--- /dev/null
+++ b/servers-gamma.h
@@ -0,0 +1,60 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef SERVERS_GAMMA_H
+#define SERVERS_GAMMA_H
+
+#include "types-output.h"
+
+#include <stddef.h>
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Handle a ‘Command: set-gamma’ message
+ *
+ * @param conn The index of the connection
+ * @param message_id The value of the ‘Message ID’ header
+ * @param crtc The value of the ‘CRTC’ header
+ * @return Zero on success (even if ignored), -1 on error,
+ * 1 if connection closed
+ */
+GCC_ONLY(__attribute__((__nonnull__(2))))
+int handle_get_gamma_info(size_t conn, const char *restrict message_id, const char *restrict crtc);
+
+/**
+ * Set the gamma ramps on an output
+ *
+ * @param output The output
+ * @param ramps The gamma ramps
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void set_gamma(const struct output *restrict output, const union gamma_ramps *restrict ramps);
+
+/**
+ * Store all current gamma ramps
+ *
+ * @return Zero on success, -1 on error
+ */
+int initialise_gamma_info(void);
+
+/**
+ * Store all current gamma ramps
+ */
+void store_gamma(void);
+
+/**
+ * Restore all gamma ramps
+ */
+void restore_gamma(void);
+
+/**
+ * Reapplu all gamma ramps
+ */
+void reapply_gamma(void);
+
+#endif
diff --git a/servers-kernel.c b/servers-kernel.c
new file mode 100644
index 0000000..fd60aef
--- /dev/null
+++ b/servers-kernel.c
@@ -0,0 +1,363 @@
+/* See LICENSE file for copyright and license details. */
+#include "servers-kernel.h"
+#include "state.h"
+#include "util.h"
+
+#include <libgamma.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/**
+ * Get the pathname of the runtime file
+ *
+ * @param suffix The suffix for the file
+ * @return The pathname of the file, `NULL` on error
+ */
+GCC_ONLY(__attribute__((__malloc__, __nonnull__)))
+static char *
+get_pathname(const char *restrict suffix)
+{
+ const char *restrict rundir = getenv("XDG_RUNTIME_DIR");
+ const char *restrict username = "";
+ char *name = NULL;
+ char *p;
+ char *restrict rc;
+ struct passwd *restrict pw;
+ size_t n;
+
+ if (sitename) {
+ name = memdup(sitename, strlen(sitename) + 1);
+ if (!name)
+ goto fail;
+ } else if ((name = libgamma_method_default_site(method))) {
+ name = memdup(name, strlen(name) + 1);
+ if (!name)
+ goto fail;
+ }
+
+ if (name) {
+ switch (method) {
+ case LIBGAMMA_METHOD_X_RANDR:
+ case LIBGAMMA_METHOD_X_VIDMODE:
+ if ((p = strrchr(name, ':')))
+ if ((p = strchr(p, '.')))
+ *p = '\0';
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!rundir || !*rundir)
+ rundir = "/tmp";
+
+ if ((pw = getpwuid(getuid())))
+ username = pw->pw_name ? pw->pw_name : "";
+
+ n = sizeof("/.coopgammad/~/.") + 3 * sizeof(int);
+ n += strlen(rundir) + strlen(username) + (name ? strlen(name) : 0) + strlen(suffix);
+ if (!(rc = malloc(n)))
+ goto fail;
+ sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s",
+ rundir, username, method, name ? "." : "", name ? name : "", suffix);
+ free(name);
+ return rc;
+
+fail:
+ free(name);
+ return NULL;
+}
+
+
+/**
+ * Get the pathname of the socket
+ *
+ * @return The pathname of the socket, `NULL` on error
+ */
+char *
+get_socket_pathname(void)
+{
+ return get_pathname(".socket");
+}
+
+
+/**
+ * Get the pathname of the PID file
+ *
+ * @return The pathname of the PID file, `NULL` on error
+ */
+char *
+get_pidfile_pathname(void)
+{
+ return get_pathname(".pid");
+}
+
+
+/**
+ * Get the pathname of the state file
+ *
+ * @return The pathname of the state file, `NULL` on error
+ */
+char *
+get_state_pathname(void)
+{
+ return get_pathname(".state");
+}
+
+
+/**
+ * Check whether a PID file is outdated
+ *
+ * @param pidpath The PID file
+ * @param token An environment variable (including both key and value)
+ * that must exist in the process if it is a coopgammad process
+ * @return -1: An error occurred
+ * 0: The service is already running
+ * 1: The PID file is outdated
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+is_pidfile_reusable(const char *restrict pidpath, const char *restrict token)
+{
+ /* PORTERS: /proc/$PID/environ is Linux specific */
+
+ char temp[sizeof("/proc//environ") + 3 * sizeof(long long int)];
+ int fd = -1, saved_errno, tries = 0;
+ char *content = NULL;
+ char *p;
+ pid_t pid = 0;
+ size_t n;
+#if defined(__linux__) || defined(HAVE_LINUX_PROCFS)
+ char *end;
+#else
+ (void) token;
+#endif
+
+ /* Get PID */
+retry:
+ fd = open(pidpath, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ content = nread(fd, &n);
+ if (!content)
+ goto fail;
+ close(fd);
+ fd = -1;
+
+ if (!n) {
+ if (++tries > 1)
+ goto bad;
+ msleep(100); /* 1 tenth of a second */
+ goto retry;
+ }
+
+ if ('0' > content[0] || content[0] > '9')
+ goto bad;
+ if (content[0] == '0' && '0' <= content[1] && content[1] <= '9')
+ goto bad;
+ for (p = content; *p; p++)
+ if ('0' <= *p && *p <= '9')
+ pid = pid * 10 + (*p & 15);
+ else
+ break;
+ if (*p++ != '\n')
+ goto bad;
+ if (*p)
+ goto bad;
+ if ((size_t)(p - content) != n)
+ goto bad;
+ sprintf(temp, "%llu\n", (unsigned long long)pid);
+ if (strcmp(content, temp))
+ goto bad;
+ free(content);
+
+ /* Validate PID */
+#if defined(__linux__) || defined(HAVE_LINUX_PROCFS)
+ sprintf(temp, "/proc/%llu/environ", (unsigned long long int)pid);
+ fd = open(temp, O_RDONLY);
+ if (fd < 0)
+ return (errno == ENOENT || errno == EACCES) ? 1 : -1;
+ content = nread(fd, &n);
+ if (!content)
+ goto fail;
+ close(fd), fd = -1;
+
+ for (end = &(p = content)[n]; p != end; p = &strchr(p, '\0')[1])
+ if (!strcmp(p, token))
+ return free(content), 0;
+ free(content);
+#else
+ if (!kill(pid, 0) || errno == EINVAL)
+ return 0;
+#endif
+
+ return 1;
+
+bad:
+ fprintf(stderr, "%s: pid file contains invalid content: %s\n", argv0, pidpath);
+ errno = 0;
+ return -1;
+
+fail:
+ saved_errno = errno;
+ free(content);
+ if (fd >= 0)
+ close(fd);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Create PID file
+ *
+ * @param pidpath The pathname of the PID file
+ * @return Zero on success, -1 on error,
+ * -2 if the service is already running
+ */
+int
+create_pidfile(char *pidpath)
+{
+ int fd = -1, r, saved_errno;
+ char *p;
+ char *restrict token = NULL;
+
+ /* Create token used to validate the service. */
+ token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath));
+ if (!token)
+ return -1;
+ sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath);
+#if !defined(USE_VALGRIND)
+ if (putenv(token))
+ goto putenv_fail;
+ /* `token` must not be free! */
+#else
+ {
+ static char static_token[sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + PATH_MAX];
+ if (strlen(pidpath) > PATH_MAX)
+ abort();
+ strcpy(static_token, token);
+ if (putenv(static_token))
+ goto fail;
+ }
+#endif
+
+ /* Create PID file's directory. */
+ for (p = pidpath; *p == '/'; p++);
+ while ((p = strchr(p, '/'))) {
+ *p = '\0';
+ if (mkdir(pidpath, 0755) < 0) {
+ if (errno != EEXIST) {
+ *p = '/';
+ goto fail;
+ }
+ }
+ *p++ = '/';
+ }
+
+ /* Create PID file. */
+retry:
+ fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644);
+ if (fd < 0) {
+ if (errno == EINTR)
+ goto retry;
+ if (errno != EEXIST)
+ return -1;
+ r = is_pidfile_reusable(pidpath, token);
+ if (r > 0) {
+ unlink(pidpath);
+ goto retry;
+ } else if (r < 0) {
+ goto fail;
+ }
+ fprintf(stderr, "%s: service is already running\n", argv0);
+ errno = 0;
+ return -2;
+ }
+
+ /* Write PID to PID file. */
+ if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
+ goto fail;
+
+ /* Done */
+#if defined(USE_VALGRIND)
+ free(token);
+#endif
+ if (close(fd) < 0)
+ if (errno != EINTR)
+ return -1;
+ return 0;
+#if !defined(USE_VALGRIND)
+putenv_fail:
+ free(token);
+#endif
+fail:
+#if defined(USE_VALGRIND)
+ free(token);
+#endif
+ if (fd >= 0) {
+ saved_errno = errno;
+ close(fd);
+ unlink(pidpath);
+ errno = saved_errno;
+ }
+ return -1;
+}
+
+
+/**
+ * Create socket and start listening
+ *
+ * @param socketpath The pathname of the socket
+ * @return Zero on success, -1 on error
+ */
+int
+create_socket(const char *socketpath)
+{
+ struct sockaddr_un address;
+
+ address.sun_family = AF_UNIX;
+ if (strlen(socketpath) >= sizeof(address.sun_path)) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ strcpy(address.sun_path, socketpath);
+ unlink(socketpath);
+ socketfd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (socketfd < 0)
+ return -1;
+ if (fchmod(socketfd, S_IRWXU) < 0)
+ return -1;
+ if (bind(socketfd, (struct sockaddr *)&address, (socklen_t)sizeof(address)) < 0)
+ return -1;
+ if (listen(socketfd, SOMAXCONN) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * Close and unlink the socket
+ *
+ * @param socketpath The pathname of the socket
+ */
+void
+close_socket(const char *socketpath)
+{
+ if (socketfd >= 0) {
+ shutdown(socketfd, SHUT_RDWR);
+ close(socketfd);
+ unlink(socketpath);
+ }
+}
diff --git a/servers-kernel.h b/servers-kernel.h
new file mode 100644
index 0000000..6839e9c
--- /dev/null
+++ b/servers-kernel.h
@@ -0,0 +1,64 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef SERVERS_KERNEL_H
+#define SERVERS_KERNEL_H
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Get the pathname of the socket
+ *
+ * @return The pathname of the socket, `NULL` on error
+ */
+GCC_ONLY(__attribute__((__malloc__)))
+char *get_socket_pathname(void);
+
+/**
+ * Get the pathname of the PID file
+ *
+ * @return The pathname of the PID file, `NULL` on error
+ */
+GCC_ONLY(__attribute__((__malloc__)))
+char *get_pidfile_pathname(void);
+
+/**
+ * Get the pathname of the state file
+ *
+ * @return The pathname of the state file, `NULL` on error
+ */
+GCC_ONLY(__attribute__((__malloc__)))
+char *get_state_pathname(void);
+
+/**
+ * Create PID file
+ *
+ * @param pidpath The pathname of the PID file
+ * @return Zero on success, -1 on error,
+ * -2 if the service is already running
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int create_pidfile(char *pidpath);
+
+/**
+ * Create socket and start listening
+ *
+ * @param socketpath The pathname of the socket
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int create_socket(const char *socketpath);
+
+/**
+ * Close and unlink the socket
+ *
+ * @param socketpath The pathname of the socket
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void close_socket(const char *socketpath);
+
+#endif
diff --git a/servers-master.c b/servers-master.c
new file mode 100644
index 0000000..f6ebfee
--- /dev/null
+++ b/servers-master.c
@@ -0,0 +1,370 @@
+/* See LICENSE file for copyright and license details. */
+#include "servers-master.h"
+#include "servers-crtc.h"
+#include "servers-gamma.h"
+#include "servers-coopgamma.h"
+#include "util.h"
+#include "communication.h"
+#include "state.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/**
+ * All poll(3p) events that are not for writing
+ */
+#define NON_WR_POLL_EVENTS (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL)
+
+
+/**
+ * Extract headers from an inbound message and pass
+ * them on to appropriate message handling function
+ *
+ * @param conn The index of the connection
+ * @param msg The inbound message
+ * @return 1: The connection as closed
+ * 0: Successful
+ * -1: Failure
+ */
+static int
+dispatch_message(size_t conn, struct message *restrict msg)
+{
+ size_t i;
+ int r = 0;
+ const char *header;
+ const char *value;
+ const char *command = NULL;
+ const char *crtc = NULL;
+ const char *coalesce = NULL;
+ const char *high_priority = NULL;
+ const char *low_priority = NULL;
+ const char *priority = NULL;
+ const char *class = NULL;
+ const char *lifespan = NULL;
+ const char *message_id = NULL;
+
+ for (i = 0; i < msg->header_count; i++) {
+ value = strstr((header = msg->headers[i]), ": ") + 2;
+ if (strstr(header, "Command: ") == header) command = value;
+ else if (strstr(header, "CRTC: ") == header) crtc = value;
+ else if (strstr(header, "Coalesce: ") == header) coalesce = value;
+ else if (strstr(header, "High priority: ") == header) high_priority = value;
+ else if (strstr(header, "Low priority: ") == header) low_priority = value;
+ else if (strstr(header, "Priority: ") == header) priority = value;
+ else if (strstr(header, "Class: ") == header) class = value;
+ else if (strstr(header, "Lifespan: ") == header) lifespan = value;
+ else if (strstr(header, "Message ID: ") == header) message_id = value;
+ else if (strstr(header, "Length: ") == header) ;/* Handled transparently */
+ else
+ fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header);
+ }
+
+ if (!command) {
+ fprintf(stderr, "%s: ignoring message without Command header\n", argv0);
+
+ } else if (!message_id) {
+ fprintf(stderr, "%s: ignoring message without Message ID header\n", argv0);
+
+ } else if (!strcmp(command, "enumerate-crtcs")) {
+ if (crtc || coalesce || high_priority || low_priority || priority || class || lifespan)
+ fprintf(stderr, "%s: ignoring superfluous headers in Command: enumerate-crtcs message\n", argv0);
+ r = handle_enumerate_crtcs(conn, message_id);
+
+ } else if (!strcmp(command, "get-gamma-info")) {
+ if (coalesce || high_priority || low_priority || priority || class || lifespan)
+ fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma-info message\n", argv0);
+ r = handle_get_gamma_info(conn, message_id, crtc);
+
+ } else if (!strcmp(command, "get-gamma")) {
+ if (priority || class || lifespan)
+ fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma message\n", argv0);
+ r = handle_get_gamma(conn, message_id, crtc, coalesce, high_priority, low_priority);
+
+ } else if (!strcmp(command, "set-gamma")) {
+ if (coalesce || high_priority || low_priority)
+ fprintf(stderr, "%s: ignoring superfluous headers in Command: set-gamma message\n", argv0);
+ r = handle_set_gamma(conn, message_id, crtc, priority, class, lifespan);
+
+ } else {
+ fprintf(stderr, "%s: ignoring unrecognised command: Command: %s\n", argv0, command);
+ }
+
+ return r;
+}
+
+
+/**
+ * Sets the file descriptor set that includes
+ * the server socket and all connections
+ *
+ * The file descriptor will be ordered as in
+ * the array `connections`, `socketfd` will
+ * be last.
+ *
+ * @param fds Reference parameter for the array of file descriptors
+ * @param fdn Output parameter for the number of file descriptors
+ * @param fds_alloc Reference parameter for the allocation size of `fds`, in elements
+ * @return Zero on success, -1 on error
+ */
+static int
+update_fdset(struct pollfd **restrict fds, nfds_t *restrict fdn, nfds_t *restrict fds_alloc)
+{
+ size_t i;
+ nfds_t j = 0;
+ void *new;
+
+ if (connections_used + 1 > *fds_alloc) {
+ new = realloc(*fds, (connections_used + 1) * sizeof(**fds));
+ if (!new)
+ return -1;
+ *fds = new;
+ *fds_alloc = connections_used + 1;
+ }
+
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] >= 0) {
+ (*fds)[j].fd = connections[i];
+ (*fds)[j].events = NON_WR_POLL_EVENTS;
+ j++;
+ }
+ }
+
+ (*fds)[j].fd = socketfd;
+ (*fds)[j].events = NON_WR_POLL_EVENTS;
+ j++;
+
+ *fdn = j;
+ return 0;
+}
+
+
+/**
+ * Handle event on the server socket
+ *
+ * @return 1: New connection accepted
+ * 0: Successful
+ * -1: Failure
+ */
+static int
+handle_server(void)
+{
+ int fd, flags, saved_errno;
+ void *new;
+
+ fd = accept(socketfd, NULL, NULL);
+ if (fd < 0) {
+ switch (errno) {
+ case ECONNABORTED:
+ case EINVAL:
+ terminate = 1;
+ /* fall through */
+ case EINTR:
+ return 0;
+ default:
+ return -1;
+ }
+ }
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+ goto fail;
+
+ if (connections_ptr == connections_alloc) {
+ new = realloc(connections, (connections_alloc + 10) * sizeof(*connections));
+ if (!new)
+ goto fail;
+ connections = new;
+ connections[connections_ptr] = fd;
+
+ new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound));
+ if (!new)
+ goto fail;
+ outbound = new;
+ ring_initialise(&outbound[connections_ptr]);
+
+ new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound));
+ if (!new)
+ goto fail;
+ inbound = new;
+ connections_alloc += 10;
+ if (message_initialise(&inbound[connections_ptr]))
+ goto fail;
+ } else {
+ connections[connections_ptr] = fd;
+ ring_initialise(&outbound[connections_ptr]);
+ if (message_initialise(&inbound[connections_ptr]))
+ goto fail;
+ }
+
+ connections_ptr++;
+ while (connections_ptr < connections_used && connections[connections_ptr] >= 0)
+ connections_ptr++;
+ if (connections_used < connections_ptr)
+ connections_used = connections_ptr;
+
+ return 1;
+fail:
+ saved_errno = errno;
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Handle event on a connection to a client
+ *
+ * @param conn The index of the connection
+ * @return 1: The connection as closed
+ * 0: Successful
+ * -1: Failure
+ */
+static int
+handle_connection(size_t conn)
+{
+ struct message *restrict msg = &inbound[conn];
+ int r, fd = connections[conn];
+
+again:
+ errno = 0;
+ switch (message_read(msg, fd)) {
+ default:
+ break;
+
+ case -1:
+ switch (errno) {
+ case EINTR:
+#if defined(EAGAIN)
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN != EWOULDBLOCK)
+ case EWOULDBLOCK:
+#endif
+ return 0;
+ default:
+ return -1;
+ case ECONNRESET:;
+ /* Fall through to `case -2` in outer switch */
+ }
+
+ case -2:
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+ connections[conn] = -1;
+ if (conn < connections_ptr)
+ connections_ptr = conn;
+ while (connections_used > 0 && connections[connections_used - 1] < 0)
+ connections_used -= 1;
+ message_destroy(msg);
+ ring_destroy(&outbound[conn]);
+ if (connection_closed(fd) < 0)
+ return -1;
+ return 1;
+ }
+
+ if ((r = dispatch_message(conn, msg)))
+ return r;
+
+ goto again;
+}
+
+
+/**
+ * Disconnect all clients
+ */
+void
+disconnect_all(void)
+{
+ size_t i;
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] >= 0) {
+ shutdown(connections[i], SHUT_RDWR);
+ close(connections[i]);
+ }
+ }
+}
+
+
+/**
+ * The program's main loop
+ *
+ * @return Zero on success, -1 on error
+ */
+int
+main_loop(void)
+{
+ struct pollfd *fds = NULL;
+ nfds_t i, fdn = 0, fds_alloc = 0;
+ int r, update, do_read, do_write, fd;
+ size_t j;
+
+ if (update_fdset(&fds, &fdn, &fds_alloc) < 0)
+ goto fail;
+
+ while (!reexec && !terminate) {
+ if (connection) {
+ if ((connection == 1 ? disconnect() : reconnect()) < 0) {
+ connection = 0;
+ goto fail;
+ }
+ connection = 0;
+ }
+
+ for (j = 0, i = 0; j < connections_used; j++) {
+ if (connections[j] >= 0) {
+ fds[i].revents = 0;
+ if (ring_have_more(outbound + j))
+ fds[(size_t)i++ + j].events |= POLLOUT;
+ else
+ fds[(size_t)i++ + j].events &= ~POLLOUT;
+ }
+ }
+ fds[i].revents = 0;
+
+ if (poll(fds, fdn, -1) < 0) {
+ if (errno == EAGAIN)
+ perror(argv0);
+ else if (errno != EINTR)
+ goto fail;
+ }
+
+ update = 0;
+ for (i = 0; i < fdn; i++) {
+ do_read = fds[i].revents & NON_WR_POLL_EVENTS;
+ do_write = fds[i].revents & POLLOUT;
+ fd = fds[i].fd;
+ if (!do_read && !do_write)
+ continue;
+
+ if (fd == socketfd) {
+ r = handle_server();
+ } else {
+ for (j = 0; connections[j] != fd; j++);
+ r = do_read ? handle_connection(j) : 0;
+ }
+
+ if (r >= 0 && do_write)
+ r |= continue_send(j);
+ if (r < 0)
+ goto fail;
+ update |= r > 0;
+ }
+ if (update && update_fdset(&fds, &fdn, &fds_alloc) < 0)
+ goto fail;
+ }
+
+ free(fds);
+ return 0;
+
+fail:
+ free(fds);
+ return -1;
+}
diff --git a/servers-master.h b/servers-master.h
new file mode 100644
index 0000000..8c49319
--- /dev/null
+++ b/servers-master.h
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef SERVERS_MASTER_H
+#define SERVERS_MASTER_H
+
+/**
+ * Disconnect all clients
+ */
+void disconnect_all(void);
+
+/**
+ * The program's main loop
+ *
+ * @return Zero on success, -1 on error
+ */
+int main_loop(void);
+
+#endif
diff --git a/src/communication.c b/src/communication.c
deleted file mode 100644
index 9ede401..0000000
--- a/src/communication.c
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "communication.h"
-#include "state.h"
-#include "servers/coopgamma.h"
-
-#include <sys/socket.h>
-#include <errno.h>
-#include <string.h>
-
-
-
-/**
- * Send a message
- *
- * @param conn The index of the connection
- * @param buf The data to send
- * @param n The size of `buf`
- * @return Zero on success, -1 on error, 1 if disconncted
- * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count
- * as success (ECONNRESET cause 1 to be returned),
- * and are handled appropriately.
- */
-int send_message(size_t conn, char* restrict buf, size_t n)
-{
- struct ring* restrict ring = outbound + conn;
- int fd = connections[conn];
- int saved_errno;
- size_t ptr = 0;
- ssize_t sent;
- size_t chunksize = n;
- size_t sendsize;
- size_t old_n;
- char* old_buf;
-
- while ((old_buf = ring_peek(ring, &old_n)))
- {
- size_t old_ptr = 0;
- while (old_ptr < n)
- {
- sendsize = old_n - old_ptr < chunksize ? old_n - old_ptr : chunksize;
- sent = send(fd, old_buf + old_ptr, sendsize, MSG_NOSIGNAL);
- if (sent < 0)
- {
- if (errno == EPIPE)
- errno = ECONNRESET;
- if (errno != EMSGSIZE)
- goto fail;
- chunksize >>= 1;
- if (chunksize == 0)
- goto fail;
- continue;
- }
- old_ptr += (size_t)sent;
- ring_pop(ring, (size_t)sent);
- }
- }
-
- while (ptr < n)
- {
- sendsize = n - ptr < chunksize ? n - ptr : chunksize;
- sent = send(fd, buf + ptr, sendsize, MSG_NOSIGNAL);
- if (sent < 0)
- {
- if (errno == EPIPE)
- errno = ECONNRESET;
- if (errno != EMSGSIZE)
- goto fail;
- chunksize >>= 1;
- if (chunksize == 0)
- goto fail;
- continue;
- }
- ptr += (size_t)sent;
- }
-
- free(buf);
- return 0;
-
- fail:
- switch (errno)
- {
- case EINTR:
- case EAGAIN:
-#if EAGAIN != EWOULDBLOCK
- case EWOULDBLOCK:
-#endif
- if (ring_push(ring, buf + ptr, n - ptr) < 0)
- goto proper_fail;
- free(buf);
- return 0;
- case ECONNRESET:
- free(buf);
- if (connection_closed(fd) < 0)
- return -1;
- return 1;
- default:
- break;
- }
- proper_fail:
- saved_errno = errno;
- free(buf);
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Send a custom error without an error number
- *
- * @param conn The index of the connection
- * @param message_id The ID of the message to which this message is a response
- * @param desc The error description to send
- * @return 1: Client disconnected
- * 0: Success (possibily delayed)
- * -1: An error occurred
- */
-int (send_error)(size_t conn, const char* restrict message_id, const char* restrict desc)
-{
- char* restrict buf;
- size_t n;
-
- MAKE_MESSAGE(&buf, &n, 0,
- "Command: error\n"
- "In response to: %s\n"
- "Error: custom\n"
- "Length: %zu\n"
- "\n"
- "%s\n",
- message_id, strlen(desc) + 1, desc);
-
- return send_message(conn, buf, n);
-}
-
-
-/**
- * Send a standard error
- *
- * @param conn The index of the connection
- * @param message_id The ID of the message to which this message is a response
- * @param number The value of `errno`, 0 to indicate success
- * @return 1: Client disconnected
- * 0: Success (possibily delayed)
- * -1: An error occurred
- */
-int (send_errno)(size_t conn, const char* restrict message_id, int number)
-{
- char* restrict buf;
- size_t n;
-
- MAKE_MESSAGE(&buf, &n, 0,
- "Command: error\n"
- "In response to: %s\n"
- "Error: %i\n"
- "\n",
- message_id, number);
-
- return send_message(conn, buf, n);
-}
-
diff --git a/src/coopgammad.c b/src/coopgammad.c
deleted file mode 100644
index e81840e..0000000
--- a/src/coopgammad.c
+++ /dev/null
@@ -1,895 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "arg.h"
-#include "util.h"
-#include "state.h"
-#include "servers/master.h"
-#include "servers/kernel.h"
-#include "servers/crtc.h"
-#include "servers/gamma.h"
-#include "servers/coopgamma.h"
-
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-
-
-/**
- * Number put in front of the marshalled data
- * so the program an detect incompatible updates
- */
-#define MARSHAL_VERSION 0
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Lists all function recognised adjustment methods,
- * will call macro X with the code for the each
- * adjustment method as the first argument and
- * corresponding name as the second argument
- */
-#define LIST_ADJUSTMENT_METHODS \
- X(LIBGAMMA_METHOD_DUMMY, "dummy") \
- X(LIBGAMMA_METHOD_X_RANDR, "randr") \
- X(LIBGAMMA_METHOD_X_VIDMODE, "vidmode") \
- X(LIBGAMMA_METHOD_LINUX_DRM, "drm") \
- X(LIBGAMMA_METHOD_W32_GDI, "gdi") \
- X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz")
-
-
-
-/**
- * Used by initialisation functions as their return type. If a
- * value not listed here is returned by such function, it is the
- * exit value the process shall exit with as soon as possible.
- */
-enum init_status
-{
- /**
- * Initialisation was successful
- */
- INIT_SUCCESS = -1,
-
- /**
- * Initialisation failed
- */
- INIT_FAILURE = -2,
-
- /**
- * Server is already running
- */
- INIT_RUNNING = -3,
-};
-
-
-
-/**
- * The pathname of the PID file
- */
-extern char* restrict pidpath;
-char* restrict pidpath = NULL;
-
-/**
- * The pathname of the socket
- */
-extern char* restrict socketpath;
-char* restrict socketpath = NULL;
-
-
-
-/**
- * Called when the process receives
- * a signal telling it to re-execute
- *
- * @param signo The received signal
- */
-static void sig_reexec(int signo)
-{
- int saved_errno = errno;
- reexec = 1;
- signal(signo, sig_reexec);
- errno = saved_errno;
-}
-
-
-/**
- * Called when the process receives
- * a signal telling it to terminate
- *
- * @param signo The received signal
- */
-static void sig_terminate(int signo)
-{
- terminate = 1;
- (void) signo;
-}
-
-
-/**
- * Called when the process receives
- * a signal telling it to disconnect
- * from or reconnect to the site
- *
- * @param signo The received signal
- */
-static void sig_connection(int signo)
-{
- int saved_errno = errno;
- connection = signo - SIGRTMIN + 1;
- signal(signo, sig_connection);
- errno = saved_errno;
-}
-
-
-/**
- * Called when the process receives
- * a signal telling it to dump its
- * state to stderr
- *
- * @param signo The received signal
- */
-static void sig_info(int signo)
-{
- int saved_errno = errno;
- char* env;
- signal(signo, sig_info);
- env = getenv("COOPGAMMAD_PIDFILE_TOKEN");
- fprintf(stderr, "PID file token: %s\n", env ? env : "(null)");
- fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)");
- fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)");
- state_dump();
- errno = saved_errno;
-}
-
-
-/**
- * Parse adjustment method name (or stringised number)
- *
- * @param arg The adjustment method name (or stringised number)
- * @return The adjustment method, -1 (negative) on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int get_method(const char* restrict arg)
-{
-#if LIBGAMMA_METHOD_MAX > 5
-# warning libgamma has added more adjustment methods
-#endif
-
- const char* restrict p;
-
-#define X(C, N) if (!strcmp(arg, N)) return C;
- LIST_ADJUSTMENT_METHODS;
-#undef X
-
- if (!*arg || (/* avoid overflow: */ strlen(arg) > 4))
- goto bad;
- for (p = arg; *p; p++)
- if (('0' > *p) || (*p > '9'))
- goto bad;
-
- return atoi(arg);
-
- bad:
- fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg);
- errno = 0;
- return -1;
-}
-
-
-/**
- * Set up signal handlers
- *
- * @return Zero on success, -1 on error
- */
-static int set_up_signals(void)
-{
- if ((signal(SIGUSR1, sig_reexec) == SIG_ERR) ||
- (signal(SIGUSR2, sig_info) == SIG_ERR) ||
-#if defined(SIGINFO)
- (signal(SIGINFO, sig_info) == SIG_ERR) ||
-#endif
- (signal(SIGTERM, sig_terminate) == SIG_ERR) ||
- (signal(SIGRTMIN + 0, sig_connection) == SIG_ERR) ||
- (signal(SIGRTMIN + 1, sig_connection) == SIG_ERR))
- return -1;
- return 0;
-}
-
-
-/**
- * Fork the process to the background
- *
- * @param keep_stderr Keep stderr open?
- * @return An `enum init_status` value or an exit value
- */
-static enum init_status daemonise(int keep_stderr)
-{
- pid_t pid;
- int fd = -1, saved_errno;
- int notify_rw[2] = { -1, -1 };
- char a_byte = 0;
- ssize_t got;
-
- if (pipe(notify_rw) < 0)
- goto fail;
- if (notify_rw[0] <= STDERR_FILENO)
- if ((notify_rw[0] = dup2atleast(notify_rw[0], STDERR_FILENO + 1)) < 0)
- goto fail;
- if (notify_rw[1] <= STDERR_FILENO)
- if ((notify_rw[1] = dup2atleast(notify_rw[1], STDERR_FILENO + 1)) < 0)
- goto fail;
-
- if ((pid = fork()) < 0)
- goto fail;
- if (pid > 0)
- {
- /* Original process (parent): */
- waitpid(pid, NULL, 0);
- close(notify_rw[1]), notify_rw[1] = -1;
- got = read(notify_rw[0], &a_byte, 1);
- if (got < 0)
- goto fail;
- close(notify_rw[0]);
- errno = 0;
- return got == 0 ? INIT_FAILURE : (enum init_status)0;
- }
-
- /* Intermediary process (child): */
- close(notify_rw[0]), notify_rw[0] = -1;
- if (setsid() < 0)
- goto fail;
- if ((pid = fork()) < 0)
- goto fail;
- if (pid > 0)
- {
- /* Intermediary process (parent): */
- return (enum init_status)0;
- }
-
- /* Daemon process (child): */
-
- /* Replace std* with /dev/null */
- fd = open("/dev/null", O_RDWR);
- if (fd < 0)
- goto fail;
-#define xdup2(s, d) do if (s != d) { close(d); if (dup2(s, d) < 0) goto fail; } while (0)
- xdup2(fd, STDIN_FILENO);
- xdup2(fd, STDOUT_FILENO);
- if (keep_stderr)
- xdup2(fd, STDERR_FILENO);
- if (fd > STDERR_FILENO)
- close(fd);
- fd = -1;
-
- /* Update PID file */
- fd = open(pidpath, O_WRONLY);
- if (fd < 0)
- goto fail;
- if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
- goto fail;
- close(fd), fd = -1;
-
- /* Notify */
- if (write(notify_rw[1], &a_byte, 1) <= 0)
- goto fail;
- close(notify_rw[1]);
-
- return INIT_SUCCESS;
- fail:
- saved_errno = errno;
- if (fd >= 0) close(fd);
- if (notify_rw[0] >= 0) close(notify_rw[0]);
- if (notify_rw[1] >= 0) close(notify_rw[1]);
- errno = saved_errno;
- return INIT_FAILURE;
-}
-
-
-/**
- * Initialise the process
- *
- * @param foreground Keep process in the foreground
- * @param keep_stderr Keep stderr open
- * @param query Was -q used, see `main` for description
- * @return An `enum init_status` value or an exit value
- */
-static enum init_status initialise(int foreground, int keep_stderr, int query)
-{
- struct rlimit rlimit;
- size_t i, n;
- sigset_t mask;
- int s;
- enum init_status r;
-
- /* Zero out some memory so it can be destoried safely. */
- memset(&site, 0, sizeof(site));
-
- if (!query)
- {
- /* Close all file descriptors above stderr */
- if (getrlimit(RLIMIT_NOFILE, &rlimit) || (rlimit.rlim_cur == RLIM_INFINITY))
- n = 4 << 10;
- else
- n = (size_t)(rlimit.rlim_cur);
- for (i = STDERR_FILENO + 1; i < n; i++)
- close((int)i);
-
- /* Set umask, reset signal handlers, and reset signal mask */
- umask(0);
- for (s = 1; s < _NSIG; s++)
- signal(s, SIG_DFL);
- if (sigfillset(&mask))
- perror(argv0);
- else
- sigprocmask(SIG_UNBLOCK, &mask, NULL);
- }
-
- /* Get method */
- if ((method < 0) && (libgamma_list_methods(&method, 1, 0) < 1))
- return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1;
-
- /* Go no further if we are just interested in the adjustment method and site */
- if (query)
- return INIT_SUCCESS;
-
- /* Get site */
- if (initialise_site() < 0)
- goto fail;
-
- /* Get PID file and socket pathname */
- if (!(pidpath = get_pidfile_pathname()) ||
- !(socketpath = get_socket_pathname()))
- goto fail;
-
- /* Create PID file */
- if ((r = create_pidfile(pidpath)) < 0)
- {
- free(pidpath), pidpath = NULL;
- if (r == -2)
- return INIT_RUNNING;
- goto fail;
- }
-
- /* Get partitions and CRTC:s */
- if (initialise_crtcs() < 0)
- goto fail;
-
- /* Get CRTC information */
- if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs))))
- goto fail;
- if (initialise_gamma_info() < 0)
- goto fail;
-
- /* Sort outputs */
- qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name);
-
- /* Load current gamma ramps */
- store_gamma();
-
- /* Preserve current gamma ramps at priority=0 if -p */
- if (preserve && (preserve_gamma() < 0))
- goto fail;
-
- /* Create socket and start listening */
- if (create_socket(socketpath) < 0)
- goto fail;
-
- /* Get the real pathname of the process's binary, in case
- * it is relative, so we can re-execute without problem. */
- if ((*argv0 != '/') && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL)))
- goto fail;
-
- /* Change directory to / to avoid blocking umounting */
- if (chdir("/") < 0)
- perror(argv0);
-
- /* Set up signal handlers */
- if (set_up_signals() < 0)
- goto fail;
-
- /* Place in the background unless -f */
- if (foreground == 0)
- return daemonise(keep_stderr);
- else
- {
- /* Signal the spawner that the service is ready */
- close(STDOUT_FILENO);
- /* Avoid potential catastrophes that would occur if a library
- * that is being used was so mindless as to write to stdout. */
- if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0)
- perror(argv0);
- }
-
- return INIT_SUCCESS;
- fail:
- return INIT_FAILURE;
-}
-
-
-/**
- * Deinitialise the process
- *
- * @param full Perform a full deinitialisation, shall be
- * done iff the process is going to re-execute
- */
-static void destroy(int full)
-{
- if (full)
- {
- disconnect_all();
- close_socket(socketpath);
- free(argv0_real);
- if ((outputs != NULL) && connected)
- restore_gamma();
- }
- state_destroy();
- free(socketpath);
- if (full && (pidpath != NULL))
- unlink(pidpath);
- free(pidpath);
-}
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal the state of the process
- *
- * @param buf Output buffer for the marshalled data,
- * `NULL` to only measure how large the
- * buffer needs to be
- * @return The number of marshalled bytes
- */
-static size_t marshal(void* restrict buf)
-{
- size_t off = 0, n;
- char* restrict bs = buf;
-
- if (bs != NULL)
- *(int*)(bs + off) = MARSHAL_VERSION;
- off += sizeof(int);
-
- n = strlen(pidpath) + 1;
- if (bs != NULL)
- memcpy(bs + off, pidpath, n);
- off += n;
-
- n = strlen(socketpath) + 1;
- if (bs != NULL)
- memcpy(bs + off, socketpath, n);
- off += n;
-
- off += state_marshal(bs ? bs + off : NULL);
-
- return off;
-}
-
-
-/**
- * Unmarshal the state of the process
- *
- * @param buf Buffer with the marshalled data
- * @return The number of marshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-static size_t unmarshal(const void* restrict buf)
-{
- size_t off = 0, n;
- const char* restrict bs = buf;
-
- if (*(const int*)(bs + off) != MARSHAL_VERSION)
- {
- fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0);
- errno = 0;
- return 0;
- }
- off += sizeof(int);
-
- n = strlen(bs + off) + 1;
- if (!(pidpath = memdup(bs + off, n)))
- return 0;
- off += n;
-
- n = strlen(bs + off) + 1;
- if (!(socketpath = memdup(bs + off, n)))
- return 0;
- off += n;
-
- off += n = state_unmarshal(bs + off);
- if (n == 0)
- return 0;
-
- return off;
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
-
-
-
-/**
- * Do minimal initialisation, unmarshal the state of
- * the process and merge with new state
- *
- * @param statefile The state file
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int restore_state(const char* restrict statefile)
-{
- void* marshalled = NULL;
- int fd = -1, saved_errno;
- size_t r, n;
-
- if (set_up_signals() < 0)
- goto fail;
-
- fd = open(statefile, O_RDONLY);
- if (fd < 0)
- goto fail;
-
- if (!(marshalled = nread(fd, &n)))
- goto fail;
- close(fd), fd = -1;
- unlink(statefile), statefile = NULL;
-
- r = unmarshal(marshalled);
- if (r == 0)
- goto fail;
- if (r != n)
- {
- fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n",
- argv0, n > r ? "larger" : "smaller", r, n);
- errno = 0;
- goto fail;
- }
- free(marshalled), marshalled = NULL;
-
- if (connected)
- {
- connected = 0;
- if (reconnect() < 0)
- goto fail;
- }
-
- return 0;
- fail:
- saved_errno = errno;
- if (fd >= 0)
- close(fd);
- free(marshalled);
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Reexecute the server
- *
- * Returns only on failure
- *
- * @return Pathname of file where the state is stored,
- * `NULL` if the state is in tact
- */
-static char* reexecute(void)
-{
- char* statefile = NULL;
- char* statebuffer = NULL;
- size_t buffer_size;
- int fd = -1, saved_errno;
-
- statefile = get_state_pathname();
- if (statefile == NULL)
- goto fail;
-
- buffer_size = marshal(NULL);
- statebuffer = malloc(buffer_size);
- if (statebuffer == NULL)
- goto fail;
- if (marshal(statebuffer) != buffer_size)
- {
- fprintf(stderr, "%s: internal error\n", argv0);
- errno = 0;
- goto fail;
- }
-
- fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
- if (fd < 0)
- goto fail;
-
- if (nwrite(fd, statebuffer, buffer_size) != buffer_size)
- goto fail;
- free(statebuffer), statebuffer = NULL;
-
- if ((close(fd) < 0) && (fd = -1, errno != EINTR))
- goto fail;
- fd = -1;
-
- destroy(0);
-
-#if !defined(USE_VALGRIND)
- execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL);
-#else
- execlp("valgrind", "valgrind", "--leak-check=full", argv0_real ? argv0_real : argv0, "- ", statefile, NULL);
-#endif
- saved_errno = errno;
- free(argv0_real), argv0_real = NULL;
- errno = saved_errno;
- return statefile;
-
- fail:
- saved_errno = errno;
- free(statebuffer);
- if (fd >= 0)
- close(fd);
- if (statefile != NULL)
- unlink(statefile), free(statefile);
- errno = saved_errno;
- return NULL;
-}
-
-
-
-/**
- * Print the response for the -q option
- *
- * @param query See -q for `main`, must be atleast 1
- * @return Zero on success, -1 on error
- */
-static int print_method_and_site(int query)
-{
- const char* restrict methodname = NULL;
- char* p;
-
- if (query == 1)
- {
- switch (method)
- {
-#define X(C, N) case C: methodname = N; break;
- LIST_ADJUSTMENT_METHODS;
-#undef X
- default:
- if (printf("%i\n", method) < 0)
- return -1;
- break;
- }
- if (methodname != NULL)
- if (printf("%s\n", methodname) < 0)
- return -1;
- }
-
- if (sitename == NULL)
- if ((sitename = libgamma_method_default_site(method)))
- if (!(sitename = memdup(sitename, strlen(sitename) + 1)))
- return -1;
-
- if (sitename != NULL)
- switch (method)
- {
- case LIBGAMMA_METHOD_X_RANDR:
- case LIBGAMMA_METHOD_X_VIDMODE:
- if ((p = strrchr(sitename, ':')))
- if ((p = strchr(p, '.')))
- *p = '\0';
- break;
- default:
- break;
- }
-
- if ((sitename != NULL) && (query == 1))
- if (printf("%s\n", sitename) < 0)
- return -1;
-
- if (query == 2)
- {
- site.method = method;
- site.site = sitename, sitename = NULL;
- socketpath = get_socket_pathname();
- if (socketpath == NULL)
- return -1;
- if (printf("%s\n", socketpath) < 0)
- return -1;
- }
-
- if (fflush(stdout))
- return -1;
- return 0;
-}
-
-
-
-/**
- * Print usage information and exit
- */
-#if defined(__GNU__) || defined(__clang__)
-__attribute__((noreturn))
-#endif
-static void usage(void)
-{
- printf("Usage: %s [-m method] [-s site] [-fkpq]\n", argv0);
- exit(1);
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command"
-#endif
-
-
-/**
- * Must not be started without stdin, stdout, or stderr (may be /dev/null)
- *
- * argv[0] must refer to the real command name or pathname,
- * otherwise, re-execute will not work
- *
- * The process closes stdout when the socket has been created
- *
- * @signal SIGUSR1 Re-execute to updated process image
- * @signal SIGUSR2 Dump the state of the process to standard error
- * @signal SIGINFO Ditto
- * @signal SIGTERM Terminate the process gracefully
- * @signal SIGRTMIN+0 Disconnect from the site
- * @signal SiGRTMIN+1 Reconnect to the site
- *
- * @param argc The number of elements in `argv`
- * @param argv Command line arguments. Recognised options:
- * -s SITENAME
- * The site to which to connect
- * -m METHOD
- * Adjustment method name or adjustment method number
- * -p
- * Preserve current gamma ramps at priority 0
- * -f
- * Do not fork the process into the background
- * -k
- * Keep stderr open
- * -q
- * Print the select (possiblity default) adjustment
- * method on the first line in stdout, and the
- * selected (possibility defasult) site on the second
- * line in stdout, and exit. If the site name is `NULL`,
- * the second line is omitted. This is indented to
- * be used by clients to figure out to which instance
- * of the service it should connect. Use twice to
- * simply ge the socket pathname, an a terminating LF.
- * By combining -q and -m you can enumerate the name
- * of all recognised adjustment method, start from 0
- * and work up until a numerical adjustment method is
- * returned.
- * @return 0: Successful
- * 1: An error occurred
- * 2: Already running
- */
-int main(int argc, char** argv)
-{
- int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r;
- char* statefile = NULL;
- char* statefile_ = NULL;
-
- ARGBEGIN
- {
- case 's':
- sitename = EARGF(usage());
- /* To simplify re-exec: */
- sitename = memdup(sitename, strlen(sitename) + 1);
- if (sitename == NULL)
- goto fail;
- break;
- case 'm':
- method = get_method(EARGF(usage()));
- if (method < 0)
- goto fail;
- break;
- case 'p': preserve = 1; break;
- case 'f': foreground = 1; break;
- case 'k': keep_stderr = 1; break;
- case 'q': query = 1 + !!query; break;
- case ' ': /* Internal, do not document */
- statefile = statefile_ = EARGF(usage());
- break;
- default:
- usage();
- }
- ARGEND;
- if (argc > 0)
- usage();
-
- restart:
-
- if (statefile == NULL)
- switch ((r = initialise(foreground, keep_stderr, query)))
- {
- case INIT_SUCCESS: break;
- case INIT_RUNNING: rc = 2; /* fall through */
- case INIT_FAILURE: goto fail;
- default: return r;
- }
- else if (restore_state(statefile) < 0)
- goto fail;
- else
- {
- if (statefile != statefile_)
- free(statefile);
- unlink(statefile), statefile = NULL;
- }
-
- if (query)
- {
- if (print_method_and_site(query) < 0)
- goto fail;
- goto done;
- }
-
- reenter_loop:
- if (main_loop() < 0)
- goto fail;
-
- if (reexec && !terminate)
- {
- if ((statefile = reexecute()))
- {
- perror(argv0);
- fprintf(stderr, "%s: restoring state without re-executing\n", argv0);
- reexec = 0;
- goto restart;
- }
- perror(argv0);
- fprintf(stderr, "%s: continuing without re-executing\n", argv0);
- reexec = 0;
- goto reenter_loop;
- }
-
- done:
- rc = 0;
- deinit:
- if (statefile)
- unlink(statefile);
- if (reexec)
- free(statefile);
- destroy(1);
- return rc;
- fail:
- if (errno != 0)
- perror(argv0);
- goto deinit;
-}
-
diff --git a/src/servers/coopgamma.c b/src/servers/coopgamma.c
deleted file mode 100644
index 6e544be..0000000
--- a/src/servers/coopgamma.c
+++ /dev/null
@@ -1,558 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "coopgamma.h"
-#include "gamma.h"
-#include "../state.h"
-#include "../communication.h"
-#include "../util.h"
-#include "../types/output.h"
-
-#include <libclut.h>
-
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * Apply a filter on top of another filter
- *
- * @param dest The output for the resulting ramp-trio, must be initialised
- * @param application The red, green and blue ramps, as one single raw array,
- * of the filter that should be applied
- * @param depth -1: `float` stops
- * -2: `double` stops
- * Other: the number of bits of each (integral) stop
- * @param base The CLUT on top of which the new filter should be applied,
- * this can be the same pointer as `dest`
- */
-static void apply_filter(union gamma_ramps* restrict dest, void* restrict application,
- int depth, union gamma_ramps* restrict base)
-{
- union gamma_ramps app;
- size_t bytedepth;
- size_t red_width, green_width, blue_width;
-
- if (depth == -1)
- bytedepth = sizeof(float);
- else if (depth == -2)
- bytedepth = sizeof(double);
- else
- bytedepth = (size_t)depth / 8;
-
- red_width = (app.u8.red_size = base->u8.red_size) * bytedepth;
- green_width = (app.u8.green_size = base->u8.green_size) * bytedepth;
- blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth;
-
- app.u8.red = application;
- app.u8.green = app.u8.red + red_width;
- app.u8.blue = app.u8.green + green_width;
-
- if (dest != base)
- {
- memcpy(dest->u8.red, base->u8.red, red_width);
- memcpy(dest->u8.green, base->u8.green, green_width);
- memcpy(dest->u8.blue, base->u8.blue, blue_width);
- }
-
- switch (depth)
- {
- case 8:
- libclut_apply(&(dest->u8), UINT8_MAX, uint8_t, &(app.u8), UINT8_MAX, uint8_t, 1, 1, 1);
- break;
- case 16:
- libclut_apply(&(dest->u16), UINT16_MAX, uint16_t, &(app.u16), UINT16_MAX, uint16_t, 1, 1, 1);
- break;
- case 32:
- libclut_apply(&(dest->u32), UINT32_MAX, uint32_t, &(app.u32), UINT32_MAX, uint32_t, 1, 1, 1);
- break;
- case 64:
- libclut_apply(&(dest->u64), UINT64_MAX, uint64_t, &(app.u64), UINT64_MAX, uint64_t, 1, 1, 1);
- break;
- case -1:
- libclut_apply(&(dest->f), 1.0f, float, &(app.d), 1.0f, float, 1, 1, 1);
- break;
- case -2:
- libclut_apply(&(dest->d), (double)1, double, &(app.f), (double)1, double, 1, 1, 1);
- break;
- default:
- abort();
- }
-}
-
-
-
-/**
- * Remove a filter from an output
- *
- * @param out The output
- * @param filter The filter
- * @return The index of the filter, `out->table_size` if not found
- */
-static ssize_t remove_filter(struct output* restrict out, struct filter* restrict filter)
-{
- size_t i, n = out->table_size;
-
- for (i = 0; i < n; i++)
- if (!strcmp(filter->class, out->table_filters[i].class))
- break;
-
- if (i == out->table_size)
- {
- fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n",
- argv0, out->name, filter->class);
- return (ssize_t)(out->table_size);
- }
-
- filter_destroy(out->table_filters + i);
- libgamma_gamma_ramps8_destroy(&(out->table_sums[i].u8));
-
- n = n - i - 1;
- memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters)));
- memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums)));
- out->table_size--;
-
- return (ssize_t)i;
-}
-
-
-/**
- * Add a filter to an output
- *
- * @param out The output
- * @param filter The filter
- * @return The index given to the filter, -1 on error
- */
-static ssize_t add_filter(struct output* restrict out, struct filter* restrict filter)
-{
- size_t i, n = out->table_size;
- int r = -1;
-
- /* Remove? */
- if (filter->lifespan == LIFESPAN_REMOVE)
- return remove_filter(out, filter);
-
- /* Update? */
- for (i = 0; i < n; i++)
- if (!strcmp(filter->class, out->table_filters[i].class))
- break;
- if (i != n)
- {
- filter_destroy(out->table_filters + i);
- out->table_filters[i] = *filter;
- filter->class = NULL;
- filter->ramps = NULL;
- return (ssize_t)i;
- }
-
- /* Add! */
- for (i = 0; i < n; i++)
- if (filter->priority > out->table_filters[i].priority)
- break;
-
- if (n == out->table_alloc)
- {
- void* new;
-
- new = realloc(out->table_filters, (n + 10) * sizeof(*(out->table_filters)));
- if (new == NULL)
- return -1;
- out->table_filters = new;
-
- new = realloc(out->table_sums, (n + 10) * sizeof(*(out->table_sums)));
- if (new == NULL)
- return -1;
- out->table_sums = new;
-
- out->table_alloc += 10;
- }
-
- memmove(out->table_filters + i + 1, out->table_filters + i, (n - i) * sizeof(*(out->table_filters)));
- memmove(out->table_sums + i + 1, out->table_sums + i, (n - i) * sizeof(*(out->table_sums)));
- out->table_size++;
-
- out->table_filters[i] = *filter;
- filter->class = NULL;
- filter->ramps = NULL;
-
- COPY_RAMP_SIZES(&(out->table_sums[i].u8), out);
- switch (out->depth)
- {
- case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break;
- case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break;
- case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break;
- case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break;
- case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break;
- case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break;
- default:
- abort();
- }
- if (r < 0)
- return -1;
-
- return (ssize_t)i;
-}
-
-
-/**
- * Handle a closed connection
- *
- * @param client The file descriptor for the client
- * @return Zero on success, -1 on error
- */
-int connection_closed(int client)
-{
- size_t i, j, k;
- int remove;
-
- for (i = 0; i < outputs_n; i++)
- {
- struct output* output = outputs + i;
- ssize_t updated = -1;
- for (j = k = 0; j < output->table_size; j += !remove, k++)
- {
- if (j != k)
- {
- output->table_filters[j] = output->table_filters[k];
- output->table_sums[j] = output->table_sums[k];
- }
- remove = output->table_filters[j].client == client;
- remove = remove && (output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH);
- if (remove)
- {
- filter_destroy(output->table_filters + j);
- libgamma_gamma_ramps8_destroy(&(output->table_sums[j].u8));
- output->table_size -= 1;
- if (updated == -1)
- updated = (ssize_t)j;
- }
- }
- if (updated >= 0)
- if (flush_filters(output, (size_t)updated) < 0)
- return -1;
- }
-
- return 0;
-}
-
-
-/**
- * Handle a ‘Command: get-gamma’ message
- *
- * @param conn The index of the connection
- * @param message_id The value of the ‘Message ID’ header
- * @param crtc The value of the ‘CRTC’ header
- * @param coalesce The value of the ‘Coalesce’ header
- * @param high_priority The value of the ‘High priority’ header
- * @param low_priority The value of the ‘Low priority’ header
- * @return Zero on success (even if ignored), -1 on error,
- * 1 if connection closed
- */
-int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
- const char* restrict coalesce, const char* restrict high_priority,
- const char* restrict low_priority)
-{
- struct output* restrict output;
- int64_t high, low;
- int coal;
- char* restrict buf;
- size_t start, end, len, n, i;
- char depth[3];
- char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)];
-
- if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
- if (coalesce == NULL) return send_error("protocol error: 'Coalesce' header omitted");
- if (high_priority == NULL) return send_error("protocol error: 'High priority' header omitted");
- if (low_priority == NULL) return send_error("protocol error: 'Low priority' header omitted");
-
- high = (int64_t)atoll(high_priority);
- low = (int64_t)atoll(low_priority);
-
- if (!strcmp(coalesce, "yes"))
- coal = 1;
- else if (!strcmp(coalesce, "no"))
- coal = 0;
- else
- return send_error("protocol error: recognised value for 'Coalesce' header");
-
- output = output_find_by_name(crtc, outputs, outputs_n);
- if (output == NULL)
- return send_error("selected CRTC does not exist");
- else if (output->supported == LIBGAMMA_NO)
- return send_error("selected CRTC does not support gamma adjustments");
-
- for (start = 0; start < output->table_size; start++)
- if (output->table_filters[start].priority <= high)
- break;
-
- for (end = output->table_size; end > 0; end--)
- if (output->table_filters[end - 1].priority >= low)
- break;
-
- switch (output->depth)
- {
- case -2: strcpy(depth, "d"); break;
- case -1: strcpy(depth, "f"); break;
- default:
- sprintf(depth, "%i", output->depth);
- break;
- }
-
- if (coal)
- {
- *tables = '\0';
- n = output->ramps_size;
- }
- else
- {
- sprintf(tables, "Tables: %zu\n", end - start);
- n = (sizeof(int64_t) + output->ramps_size) * (end - start);
- for (i = start; i < end; i++)
- n += strlen(output->table_filters[i].class) + 1;
- }
-
- MAKE_MESSAGE(&buf, &n, n,
- "In response to: %s\n"
- "Depth: %s\n"
- "Red size: %zu\n"
- "Green size: %zu\n"
- "Blue size: %zu\n"
- "%s"
- "Length: %zu\n"
- "\n",
- message_id, depth, output->red_size, output->green_size,
- output->blue_size, tables, n);
-
- if (coal)
- {
- if ((start == 0) && (start < end))
- memcpy(buf + n, output->table_sums[end - 1].u8.red, output->ramps_size);
- else
- {
- union gamma_ramps ramps;
- if (make_plain_ramps(&ramps, output))
- {
- int saved_errno = errno;
- free(buf);
- errno = saved_errno;
- return -1;
- }
- for (i = start; i < end; i++)
- apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps);
- memcpy(buf + n, ramps.u8.red, output->ramps_size);
- libgamma_gamma_ramps8_destroy(&(ramps.u8));
- }
- n += output->ramps_size;
- }
- else
- for (i = start; i < end; i++)
- {
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
- *(int64_t*)(buf + n) = output->table_filters[i].priority;
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
- n += sizeof(int64_t);
- len = strlen(output->table_filters[i].class) + 1;
- memcpy(buf + n, output->table_filters[i].class, len);
- n += len;
- memcpy(buf + n, output->table_filters[i].ramps, output->ramps_size);
- n += output->ramps_size;
- }
-
- return send_message(conn, buf, n);
-}
-
-
-/**
- * Handle a ‘Command: set-gamma’ message
- *
- * @param conn The index of the connection
- * @param message_id The value of the ‘Message ID’ header
- * @param crtc The value of the ‘CRTC’ header
- * @param priority The value of the ‘Priority’ header
- * @param class The value of the ‘Class’ header
- * @param lifespan The value of the ‘Lifespan’ header
- * @return Zero on success (even if ignored), -1 on error,
- * 1 if connection closed
- */
-int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
- const char* restrict priority, const char* restrict class, const char* restrict lifespan)
-{
- struct message* restrict msg = inbound + conn;
- struct output* restrict output = NULL;
- struct filter filter;
- char* restrict p;
- char* restrict q;
- int saved_errno;
- ssize_t r;
-
- if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
- if (class == NULL) return send_error("protocol error: 'Class' header omitted");
- if (lifespan == NULL) return send_error("protocol error: 'Lifespan' header omitted");
-
- filter.client = connections[conn];
- filter.priority = priority == NULL ? 0 : (int64_t)atoll(priority);
- filter.ramps = NULL;
-
- output = output_find_by_name(crtc, outputs, outputs_n);
- if (output == NULL)
- return send_error("CRTC does not exists");
-
- p = strstr(class, "::");
- if ((p == NULL) || (p == class))
- return send_error("protocol error: malformatted value for 'Class' header");
- q = strstr(p + 2, "::");
- if ((q == NULL) || (q == p))
- return send_error("protocol error: malformatted value for 'Class' header");
-
- if (!strcmp(lifespan, "until-removal"))
- filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
- else if (!strcmp(lifespan, "until-death"))
- filter.lifespan = LIFESPAN_UNTIL_DEATH;
- else if (!strcmp(lifespan, "remove"))
- filter.lifespan = LIFESPAN_REMOVE;
- else
- return send_error("protocol error: recognised value for 'Lifespan' header");
-
- if (filter.lifespan == LIFESPAN_REMOVE)
- {
- if (msg->payload_size)
- fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with "
- "Lifespan: remove\n", argv0);
- if (priority != NULL)
- fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with "
- "Lifespan: remove\n", argv0);
- }
- else if (msg->payload_size != output->ramps_size)
- return send_error("invalid payload: size of message payload does matched the expectancy");
- else if (priority == NULL)
- return send_error("protocol error: 'Priority' header omitted");
-
- filter.class = memdup(class, strlen(class) + 1);
- if (filter.class == NULL)
- goto fail;
-
- if (filter.lifespan != LIFESPAN_REMOVE)
- {
- filter.ramps = memdup(msg->payload, msg->payload_size);
- if (filter.ramps == NULL)
- goto fail;
- }
-
- if ((r = add_filter(output, &filter)) < 0)
- goto fail;
- if (flush_filters(output, (size_t)r))
- goto fail;
-
- free(filter.class);
- free(filter.ramps);
- return send_errno(0);
-
- fail:
- saved_errno = errno;
- send_errno(saved_errno);
- free(filter.class);
- free(filter.ramps);
- errno = saved_errno;
- return -1;
-}
-
-
-
-/**
- * Recalculate the resulting gamma and
- * update push the new gamma ramps to the CRTC
- *
- * @param output The output
- * @param first_updated The index of the first added or removed filter
- * @return Zero on success, -1 on error
- */
-int flush_filters(struct output* restrict output, size_t first_updated)
-{
- union gamma_ramps plain;
- union gamma_ramps* last;
- size_t i;
-
- if (first_updated == 0)
- {
- if (make_plain_ramps(&plain, output) < 0)
- return -1;
- last = &plain;
- }
- else
- last = output->table_sums + (first_updated - 1);
-
- for (i = first_updated; i < output->table_size; i++)
- {
- apply_filter(output->table_sums + i, output->table_filters[i].ramps, output->depth, last);
- last = output->table_sums + i;
- }
-
- set_gamma(output, last);
-
- if (first_updated == 0)
- libgamma_gamma_ramps8_destroy(&(plain.u8));
-
- return 0;
-}
-
-
-
-/**
- * Preserve current gamma ramps at priority 0 for all outputs
- *
- * @return Zero on success, -1 on error
- */
-int preserve_gamma(void)
-{
- size_t i;
-
- for (i = 0; i < outputs_n; i++)
- {
- struct filter filter = {
- .client = -1,
- .priority = 0,
- .class = NULL,
- .lifespan = LIFESPAN_UNTIL_REMOVAL,
- .ramps = NULL
- };
- outputs[i].table_filters = calloc(4, sizeof(*(outputs[i].table_filters)));
- outputs[i].table_sums = calloc(4, sizeof(*(outputs[i].table_sums)));
- outputs[i].table_alloc = 4;
- outputs[i].table_size = 1;
- filter.class = memdup(PKGNAME "::" COMMAND "::preserved", sizeof(PKGNAME "::" COMMAND "::preserved"));
- if (filter.class == NULL)
- return -1;
- filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size);
- if (filter.ramps == NULL)
- return -1;
- outputs[i].table_filters[0] = filter;
- COPY_RAMP_SIZES(&(outputs[i].table_sums[0].u8), outputs + i);
- if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size))
- return -1;
- }
-
- return 0;
-}
-
diff --git a/src/servers/crtc.c b/src/servers/crtc.c
deleted file mode 100644
index ca10940..0000000
--- a/src/servers/crtc.c
+++ /dev/null
@@ -1,325 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "crtc.h"
-#include "gamma.h"
-#include "coopgamma.h"
-#include "../state.h"
-#include "../communication.h"
-#include "../util.h"
-
-#include <errno.h>
-#include <string.h>
-
-
-
-/**
- * Handle a ‘Command: enumerate-crtcs’ message
- *
- * @param conn The index of the connection
- * @param message_id The value of the ‘Message ID’ header
- * @return Zero on success (even if ignored), -1 on error,
- * 1 if connection closed
- */
-int handle_enumerate_crtcs(size_t conn, const char* restrict message_id)
-{
- size_t i, n = 0, len;
- char* restrict buf;
-
- for (i = 0; i < outputs_n; i++)
- n += strlen(outputs[i].name) + 1;
-
- MAKE_MESSAGE(&buf, &n, n,
- "Command: crtc-enumeration\n"
- "In response to: %s\n"
- "Length: %zu\n"
- "\n",
- message_id, n);
-
- for (i = 0; i < outputs_n; i++)
- {
- len = strlen(outputs[i].name);
- memcpy(buf + n, outputs[i].name, len);
- buf[n + len] = '\n';
- n += len + 1;
- }
-
- return send_message(conn, buf, n);
-}
-
-
-/**
- * Get the name of a CRTC
- *
- * @param info Information about the CRTC
- * @param crtc libgamma's state for the CRTC
- * @return The name of the CRTC, `NULL` on error
- */
-char* get_crtc_name(const libgamma_crtc_information_t* restrict info,
- const libgamma_crtc_state_t* restrict crtc)
-{
- if ((info->edid_error == 0) && (info->edid != NULL))
- return libgamma_behex_edid(info->edid, info->edid_length);
- else if ((info->connector_name_error == 0) && (info->connector_name != NULL))
- {
- char* name = malloc(3 * sizeof(size_t) + strlen(info->connector_name) + 2);
- if (name != NULL)
- sprintf(name, "%zu.%s", crtc->partition->partition, info->connector_name);
- return name;
- }
- else
- {
- char* name = malloc(2 * 3 * sizeof(size_t) + 2);
- if (name != NULL)
- sprintf(name, "%zu.%zu", crtc->partition->partition, crtc->crtc);
- return name;
- }
-}
-
-
-/**
- * Initialise the site
- *
- * @return Zero on success, -1 on error
- */
-int initialise_site(void)
-{
- char* restrict sitename_dup = NULL;
- int gerror, saved_errno;
-
- if ((sitename != NULL) && !(sitename_dup = memdup(sitename, strlen(sitename) + 1)))
- goto fail;
- if ((gerror = libgamma_site_initialise(&site, method, sitename_dup)))
- goto fail_libgamma;
-
- return 0;
- fail_libgamma:
- sitename_dup = NULL;
- libgamma_perror(argv0, gerror);
- errno = 0;
- fail:
- saved_errno = errno;
- free(sitename_dup);
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Get partitions and CRTC:s
- *
- * @return Zero on success, -1 on error
- */
-int initialise_crtcs(void)
-{
- size_t i, j, n, n0;
- int gerror;
-
- /* Get partitions */
- outputs_n = 0;
- if (site.partitions_available)
- if (!(partitions = calloc(site.partitions_available, sizeof(*partitions))))
- goto fail;
- for (i = 0; i < site.partitions_available; i++)
- {
- if ((gerror = libgamma_partition_initialise(partitions + i, &site, i)))
- goto fail_libgamma;
- outputs_n += partitions[i].crtcs_available;
- }
-
- /* Get CRTC:s */
- if (outputs_n)
- if (!(crtcs = calloc(outputs_n, sizeof(*crtcs))))
- goto fail;
- for (i = 0, j = n = 0; i < site.partitions_available; i++)
- for (n0 = n, n += partitions[i].crtcs_available; j < n; j++)
- if ((gerror = libgamma_crtc_initialise(crtcs + j, partitions + i, j - n0)))
- goto fail_libgamma;
-
- return 0;
-
- fail_libgamma:
- libgamma_perror(argv0, gerror);
- errno = 0;
- fail:
- return -1;
-}
-
-
-/**
- * Merge the new state with an old state
- *
- * @param old_outputs The old `outputs`
- * @param old_outputs_n The old `outputs_n`
- * @return Zero on success, -1 on error
- */
-int merge_state(struct output* restrict old_outputs, size_t old_outputs_n)
-{
- struct output* restrict new_outputs = NULL;
- size_t new_outputs_n;
- size_t i, j;
-
- /* How many outputs does the system now have? */
- i = j = new_outputs_n = 0;
- while ((i < old_outputs_n) && (j < outputs_n))
- {
- int cmp = strcmp(old_outputs[i].name, outputs[j].name);
- if (cmp <= 0)
- new_outputs_n++;
- i += cmp >= 0;
- j += cmp <= 0;
- }
- new_outputs_n += outputs_n - j;
-
- /* Allocate output state array */
- if (new_outputs_n > 0)
- {
- new_outputs = calloc(new_outputs_n, sizeof(*new_outputs));
- if (new_outputs == NULL)
- return -1;
- }
-
- /* Merge output states */
- i = j = new_outputs_n = 0;
- while ((i < old_outputs_n) && (j < outputs_n))
- {
- int is_same = 0, cmp = strcmp(old_outputs[i].name, outputs[j].name);
- if (cmp == 0)
- is_same = (old_outputs[i].depth == outputs[j].depth &&
- old_outputs[i].red_size == outputs[j].red_size &&
- old_outputs[i].green_size == outputs[j].green_size &&
- old_outputs[i].blue_size == outputs[j].blue_size);
- if (is_same)
- {
- new_outputs[new_outputs_n] = old_outputs[i];
- new_outputs[new_outputs_n].crtc = outputs[j].crtc;
- memset(old_outputs + i, 0, sizeof(*old_outputs));
- outputs[j].crtc = NULL;
- output_destroy(outputs + j);
- new_outputs_n++;
- }
- else if (cmp <= 0)
- new_outputs[new_outputs_n++] = outputs[j];
- i += cmp >= 0;
- j += cmp <= 0;
- }
- while (j < outputs_n)
- new_outputs[new_outputs_n++] = outputs[j++];
-
- /* Commit merge */
- free(outputs);
- outputs = new_outputs;
- outputs_n = new_outputs_n;
-
- return 0;
-}
-
-
-
-/**
- * Disconnect from the site
- *
- * @return Zero on success, -1 on error
- */
-int disconnect(void)
-{
- size_t i;
-
- if (!connected)
- return 0;
- connected = 0;
-
- for (i = 0; i < outputs_n; i++)
- {
- outputs[i].crtc = NULL;
- libgamma_crtc_destroy(crtcs + i);
- }
- free(crtcs), crtcs = NULL;
- for (i = 0; i < site.partitions_available; i++)
- libgamma_partition_destroy(partitions + i);
- free(partitions), partitions = NULL;
- libgamma_site_destroy(&site);
- memset(&site, 0, sizeof(site));
-
- return 0;
-}
-
-
-/**
- * Reconnect to the site
- *
- * @return Zero on success, -1 on error
- */
-int reconnect(void)
-{
- struct output* restrict old_outputs;
- size_t i, old_outputs_n;
- int saved_errno;
-
- if (connected)
- return 0;
- connected = 1;
-
- /* Remember old state */
- old_outputs = outputs, outputs = NULL;
- old_outputs_n = outputs_n, outputs_n = 0;
-
- /* Get site */
- if (initialise_site() < 0)
- goto fail;
-
- /* Get partitions and CRTC:s */
- if (initialise_crtcs() < 0)
- goto fail;
-
- /* Get CRTC information */
- if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs))))
- goto fail;
- if (initialise_gamma_info() < 0)
- goto fail;
-
- /* Sort outputs */
- qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name);
-
- /* Load current gamma ramps */
- store_gamma();
-
- /* Preserve current gamma ramps at priority=0 if -p */
- if (preserve && (preserve_gamma() < 0))
- goto fail;
-
- /* Merge state */
- if (merge_state(old_outputs, old_outputs_n) < 0)
- goto fail;
- for (i = 0; i < old_outputs_n; i++)
- output_destroy(old_outputs + i);
- free(old_outputs), old_outputs = NULL, old_outputs_n = 0;
-
- /* Reapply gamma ramps */
- reapply_gamma();
-
- return 0;
-
- fail:
- saved_errno = errno;
- for (i = 0; i < old_outputs_n; i++)
- output_destroy(old_outputs + i);
- free(old_outputs);
- errno = saved_errno;
- return -1;
-}
-
diff --git a/src/servers/gamma.c b/src/servers/gamma.c
deleted file mode 100644
index 17105d4..0000000
--- a/src/servers/gamma.c
+++ /dev/null
@@ -1,398 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "gamma.h"
-#include "crtc.h"
-#include "../state.h"
-#include "../communication.h"
-#include "../util.h"
-
-#include <errno.h>
-#include <string.h>
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic ignored "-Wswitch-enum"
-#endif
-
-
-
-/**
- * Handle a ‘Command: set-gamma’ message
- *
- * @param conn The index of the connection
- * @param message_id The value of the ‘Message ID’ header
- * @param crtc The value of the ‘CRTC’ header
- * @return Zero on success (even if ignored), -1 on error,
- * 1 if connection closed
- */
-int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc)
-{
- struct output* restrict output;
- char* restrict buf;
- char depth[3];
- const char* supported;
- const char* colourspace;
- char gamut[8 * sizeof("White x: 1023")];
- size_t n;
-
- if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted");
-
- output = output_find_by_name(crtc, outputs, outputs_n);
- if (output == NULL)
- return send_error("selected CRTC does not exist");
-
- switch (output->depth)
- {
- case -2: strcpy(depth, "d"); break;
- case -1: strcpy(depth, "f"); break;
- default:
- sprintf(depth, "%i", output->depth);
- break;
- }
-
- switch (output->supported)
- {
- case LIBGAMMA_YES: supported = "yes"; break;
- case LIBGAMMA_NO: supported = "no"; break;
- default: supported = "maybe"; break;
- }
-
- switch (output->colourspace)
- {
- case COLOURSPACE_SRGB_SANS_GAMUT:
- case COLOURSPACE_SRGB: colourspace = "Colour space: sRGB\n"; break;
- case COLOURSPACE_RGB_SANS_GAMUT:
- case COLOURSPACE_RGB: colourspace = "Colour space: RGB\n"; break;
- case COLOURSPACE_NON_RGB: colourspace = "Colour space: non-RGB\n"; break;
- case COLOURSPACE_GREY: colourspace = "Colour space: grey\n"; break;
- default: colourspace = ""; break;
- }
-
- switch (output->colourspace)
- {
- case COLOURSPACE_SRGB:
- case COLOURSPACE_RGB:
- sprintf(gamut,
- "Red x: %u\n"
- "Red y: %u\n"
- "Green x: %u\n"
- "Green y: %u\n"
- "Blue x: %u\n"
- "Blue y: %u\n"
- "White x: %u\n"
- "White y: %u\n",
- output->red_x, output->red_y, output->green_x, output->green_y,
- output->blue_x, output->blue_y, output->white_x, output->white_y);
- break;
- default:
- *gamut = '\0';
- break;
- }
-
- MAKE_MESSAGE(&buf, &n, 0,
- "In response to: %s\n"
- "Cooperative: yes\n" /* In mds: say ‘no’, mds-coopgamma changes to ‘yes’.” */
- "Depth: %s\n"
- "Red size: %zu\n"
- "Green size: %zu\n"
- "Blue size: %zu\n"
- "Gamma support: %s\n"
- "%s%s"
- "\n",
- message_id, depth, output->red_size, output->green_size,
- output->blue_size, supported, gamut, colourspace);
-
- return send_message(conn, buf, n);
-}
-
-
-/**
- * Set the gamma ramps on an output
- *
- * @param output The output
- * @param ramps The gamma ramps
- */
-void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps)
-{
- int r = 0;
-
- if (!connected)
- return;
-
- switch (output->depth)
- {
- case 8: r = libgamma_crtc_set_gamma_ramps8(output->crtc, ramps->u8); break;
- case 16: r = libgamma_crtc_set_gamma_ramps16(output->crtc, ramps->u16); break;
- case 32: r = libgamma_crtc_set_gamma_ramps32(output->crtc, ramps->u32); break;
- case 64: r = libgamma_crtc_set_gamma_ramps64(output->crtc, ramps->u64); break;
- case -1: r = libgamma_crtc_set_gamma_rampsf(output->crtc, ramps->f); break;
- case -2: r = libgamma_crtc_set_gamma_rampsd(output->crtc, ramps->d); break;
- default:
- abort();
- }
- if (r)
- libgamma_perror(argv0, r); /* Not fatal */
-}
-
-
-/**
- * Parse the EDID of a monitor
- *
- * @param output The output
- * @param edid The EDID in binary format
- * @param n The length of the EDID
- */
-static void parse_edid(struct output* restrict output, const unsigned char* restrict edid, size_t n)
-{
- size_t i;
- int analogue;
- unsigned sum;
-
- output->red_x = output->green_x = output->blue_x = output->white_x = 0;
- output->red_y = output->green_y = output->blue_y = output->white_y = 0;
- output->colourspace = COLOURSPACE_UNKNOWN;
-
- if ((edid == NULL) || (n < 128))
- return;
- for (i = 0, sum = 0; i < 128; i++)
- sum += (unsigned)edid[i];
- if ((sum & 0xFF) != 0)
- return;
- if ((edid[0] != 0) || (edid[7] != 0))
- return;
- for (i = 1; i < 7; i++)
- if (edid[i] != 0xFF)
- return;
-
- analogue = !(edid[20] & 0x80);
- if (!analogue)
- output->colourspace = COLOURSPACE_RGB;
- else
- switch ((edid[24] >> 3) & 3)
- {
- case 0: output->colourspace = COLOURSPACE_GREY; break;
- case 1: output->colourspace = COLOURSPACE_RGB; break;
- case 2: output->colourspace = COLOURSPACE_NON_RGB; break;
- default: output->colourspace = COLOURSPACE_UNKNOWN; break;
- }
-
- if (output->colourspace != COLOURSPACE_RGB)
- return;
-
- if (edid[24] & 4)
- output->colourspace = COLOURSPACE_SRGB;
-
- output->red_x = (edid[25] >> 6) & 3;
- output->red_y = (edid[25] >> 4) & 3;
- output->green_x = (edid[25] >> 2) & 3;
- output->green_y = (edid[25] >> 0) & 3;
- output->blue_x = (edid[26] >> 6) & 3;
- output->blue_y = (edid[26] >> 4) & 3;
- output->white_x = (edid[26] >> 2) & 3;
- output->white_y = (edid[26] >> 0) & 3;
-
- output->red_x |= ((unsigned)(edid[27])) << 2;
- output->red_y |= ((unsigned)(edid[28])) << 2;
- output->green_x |= ((unsigned)(edid[29])) << 2;
- output->green_y |= ((unsigned)(edid[30])) << 2;
- output->blue_x |= ((unsigned)(edid[31])) << 2;
- output->blue_y |= ((unsigned)(edid[32])) << 2;
- output->white_x |= ((unsigned)(edid[33])) << 2;
- output->white_y |= ((unsigned)(edid[34])) << 2;
-
- if ((output->red_x == output->red_y) &&
- (output->red_x == output->green_x) &&
- (output->red_x == output->green_y) &&
- (output->red_x == output->blue_x) &&
- (output->red_x == output->blue_y) &&
- (output->red_x == output->white_x) &&
- (output->red_x == output->white_y))
- {
- if (output->colourspace == COLOURSPACE_SRGB)
- output->colourspace = COLOURSPACE_SRGB_SANS_GAMUT;
- else
- output->colourspace = COLOURSPACE_RGB_SANS_GAMUT;
- }
-}
-
-
-/**
- * Store all current gamma ramps
- *
- * @return Zero on success, -1 on error
- */
-int initialise_gamma_info(void)
-{
- libgamma_crtc_information_t info;
- int saved_errno;
- size_t i;
-
- for (i = 0; i < outputs_n; i++)
- {
- libgamma_get_crtc_information(&info, crtcs + i,
- LIBGAMMA_CRTC_INFO_EDID |
- LIBGAMMA_CRTC_INFO_MACRO_RAMP |
- LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT |
- LIBGAMMA_CRTC_INFO_CONNECTOR_NAME);
- outputs[i].depth = info.gamma_depth_error ? 0 : info.gamma_depth;
- outputs[i].red_size = info.gamma_size_error ? 0 : info.red_gamma_size;
- outputs[i].green_size = info.gamma_size_error ? 0 : info.green_gamma_size;
- outputs[i].blue_size = info.gamma_size_error ? 0 : info.blue_gamma_size;
- outputs[i].supported = info.gamma_support_error ? 0 : info.gamma_support;
- if (info.gamma_support_error == LIBGAMMA_CRTC_INFO_NOT_SUPPORTED)
- outputs[i].supported = LIBGAMMA_MAYBE;
- if (outputs[i].depth == 0 || outputs[i].red_size == 0 ||
- outputs[i].green_size == 0 || outputs[i].blue_size == 0)
- outputs[i].supported = 0;
- parse_edid(outputs + i, info.edid_error ? NULL : info.edid, info.edid_error ? 0 : info.edid_length);
- outputs[i].name = get_crtc_name(&info, crtcs + i);
- saved_errno = errno;
- outputs[i].name_is_edid = ((info.edid_error == 0) && (info.edid != NULL));
- outputs[i].crtc = crtcs + i;
- libgamma_crtc_information_destroy(&info);
- outputs[i].ramps_size = outputs[i].red_size + outputs[i].green_size + outputs[i].blue_size;
- switch (outputs[i].depth)
- {
- default:
- outputs[i].depth = 64;
- /* Fall through */
- case 8:
- case 16:
- case 32:
- case 64: outputs[i].ramps_size *= (size_t)(outputs[i].depth / 8); break;
- case -2: outputs[i].ramps_size *= sizeof(double); break;
- case -1: outputs[i].ramps_size *= sizeof(float); break;
- }
- errno = saved_errno;
- if (outputs[i].name == NULL)
- return -1;
- }
-
- return 0;
-}
-
-
-/**
- * Store all current gamma ramps
- */
-void store_gamma(void)
-{
- int gerror;
- size_t i;
-
-#define LOAD_RAMPS(SUFFIX, MEMBER) \
- do \
- { \
- libgamma_gamma_ramps##SUFFIX##_initialise(&(outputs[i].saved_ramps.MEMBER)); \
- gerror = libgamma_crtc_get_gamma_ramps##SUFFIX(outputs[i].crtc, &(outputs[i].saved_ramps.MEMBER)); \
- if (gerror) \
- { \
- libgamma_perror(argv0, gerror); \
- outputs[i].supported = LIBGAMMA_NO; \
- libgamma_gamma_ramps##SUFFIX##_destroy(&(outputs[i].saved_ramps.MEMBER)); \
- memset(&(outputs[i].saved_ramps.MEMBER), 0, sizeof(outputs[i].saved_ramps.MEMBER)); \
- } \
- } \
- while (0)
-
- for (i = 0; i < outputs_n; i++)
- {
- if (outputs[i].supported == LIBGAMMA_NO)
- continue;
-
- outputs[i].saved_ramps.u8.red_size = outputs[i].red_size;
- outputs[i].saved_ramps.u8.green_size = outputs[i].green_size;
- outputs[i].saved_ramps.u8.blue_size = outputs[i].blue_size;
-
- switch (outputs[i].depth)
- {
- case 64: LOAD_RAMPS(64, u64); break;
- case 32: LOAD_RAMPS(32, u32); break;
- case 16: LOAD_RAMPS(16, u16); break;
- case 8: LOAD_RAMPS( 8, u8); break;
- case -2: LOAD_RAMPS(d, d); break;
- case -1: LOAD_RAMPS(f, f); break;
- default: /* impossible */ break;
- }
- }
-}
-
-
-/**
- * Restore all gamma ramps
- */
-void restore_gamma(void)
-{
- size_t i;
- int gerror;
-
-#define RESTORE_RAMPS(SUFFIX, MEMBER) \
- do \
- if (outputs[i].saved_ramps.MEMBER.red != NULL) \
- { \
- gerror = libgamma_crtc_set_gamma_ramps##SUFFIX(outputs[i].crtc, outputs[i].saved_ramps.MEMBER); \
- if (gerror) \
- libgamma_perror(argv0, gerror); \
- } \
- while (0)
-
- for (i = 0; i < outputs_n; i++)
- {
- if (outputs[i].supported == LIBGAMMA_NO)
- continue;
- if (outputs[i].saved_ramps.u8.red == NULL)
- continue;
-
- switch (outputs[i].depth)
- {
- case 64: RESTORE_RAMPS(64, u64); break;
- case 32: RESTORE_RAMPS(32, u32); break;
- case 16: RESTORE_RAMPS(16, u16); break;
- case 8: RESTORE_RAMPS( 8, u8); break;
- case -2: RESTORE_RAMPS(d, d); break;
- case -1: RESTORE_RAMPS(f, f); break;
- default: /* impossible */ break;
- }
- }
-}
-
-
-/**
- * Reapplu all gamma ramps
- */
-void reapply_gamma(void)
-{
- union gamma_ramps plain;
- size_t i;
-
- /* Reapply gamma ramps */
- for (i = 0; i < outputs_n; i++)
- {
- struct output* output = outputs + i;
- if (output->table_size > 0)
- set_gamma(output, output->table_sums + output->table_size - 1);
- else
- {
- make_plain_ramps(&plain, output);
- set_gamma(output, &plain);
- libgamma_gamma_ramps8_destroy(&(plain.u8));
- }
- }
-}
-
diff --git a/src/servers/gamma.h b/src/servers/gamma.h
deleted file mode 100644
index ac269e0..0000000
--- a/src/servers/gamma.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef SERVERS_GAMMA_H
-#define SERVERS_GAMMA_H
-
-
-#include "../types/output.h"
-
-#include <stddef.h>
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Handle a ‘Command: set-gamma’ message
- *
- * @param conn The index of the connection
- * @param message_id The value of the ‘Message ID’ header
- * @param crtc The value of the ‘CRTC’ header
- * @return Zero on success (even if ignored), -1 on error,
- * 1 if connection closed
- */
-GCC_ONLY(__attribute__((nonnull(2))))
-int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc);
-
-/**
- * Set the gamma ramps on an output
- *
- * @param output The output
- * @param ramps The gamma ramps
- */
-GCC_ONLY(__attribute__((nonnull)))
-void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps);
-
-
-/**
- * Store all current gamma ramps
- *
- * @return Zero on success, -1 on error
- */
-int initialise_gamma_info(void);
-
-/**
- * Store all current gamma ramps
- */
-void store_gamma(void);
-
-/**
- * Restore all gamma ramps
- */
-void restore_gamma(void);
-
-
-/**
- * Reapplu all gamma ramps
- */
-void reapply_gamma(void);
-
-
-#endif
-
diff --git a/src/servers/kernel.c b/src/servers/kernel.c
deleted file mode 100644
index 1600d0a..0000000
--- a/src/servers/kernel.c
+++ /dev/null
@@ -1,384 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "kernel.h"
-#include "../state.h"
-#include "../util.h"
-
-#include <libgamma.h>
-
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <pwd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-
-
-/**
- * Get the pathname of the runtime file
- *
- * @param suffix The suffix for the file
- * @return The pathname of the file, `NULL` on error
- */
-GCC_ONLY(__attribute__((malloc, nonnull)))
-static char* get_pathname(const char* restrict suffix)
-{
- const char* restrict rundir = getenv("XDG_RUNTIME_DIR");
- const char* restrict username = "";
- char* name = NULL;
- char* p;
- char* restrict rc;
- struct passwd* restrict pw;
- size_t n;
- int saved_errno;
-
- if (sitename)
- {
- name = memdup(sitename, strlen(sitename) + 1);
- if (name == NULL)
- goto fail;
- }
- else if ((name = libgamma_method_default_site(method)))
- {
- name = memdup(name, strlen(name) + 1);
- if (name == NULL)
- goto fail;
- }
-
- if (name != NULL)
- switch (method)
- {
- case LIBGAMMA_METHOD_X_RANDR:
- case LIBGAMMA_METHOD_X_VIDMODE:
- if ((p = strrchr(name, ':')))
- if ((p = strchr(p, '.')))
- *p = '\0';
- break;
- default:
- break;
- }
-
- if (!rundir || !*rundir)
- rundir = "/tmp";
-
- if ((pw = getpwuid(getuid())))
- username = pw->pw_name ? pw->pw_name : "";
-
- n = sizeof("/.coopgammad/~/.") + 3 * sizeof(int);
- n += strlen(rundir) + strlen(username) + ((name != NULL) ? strlen(name) : 0) + strlen(suffix);
- if (!(rc = malloc(n)))
- goto fail;
- sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s",
- rundir, username, method, name ? "." : "", name ? name : "", suffix);
- free(name);
- return rc;
-
- fail:
- saved_errno = errno;
- free(name);
- errno = saved_errno;
- return NULL;
-}
-
-
-/**
- * Get the pathname of the socket
- *
- * @return The pathname of the socket, `NULL` on error
- */
-char* get_socket_pathname(void)
-{
- return get_pathname(".socket");
-}
-
-
-/**
- * Get the pathname of the PID file
- *
- * @return The pathname of the PID file, `NULL` on error
- */
-char* get_pidfile_pathname(void)
-{
- return get_pathname(".pid");
-}
-
-
-/**
- * Get the pathname of the state file
- *
- * @return The pathname of the state file, `NULL` on error
- */
-char* get_state_pathname(void)
-{
- return get_pathname(".state");
-}
-
-
-/**
- * Check whether a PID file is outdated
- *
- * @param pidpath The PID file
- * @param token An environment variable (including both key and value)
- * that must exist in the process if it is a coopgammad process
- * @return -1: An error occurred
- * 0: The service is already running
- * 1: The PID file is outdated
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int is_pidfile_reusable(const char* restrict pidpath, const char* restrict token)
-{
- /* PORTERS: /proc/$PID/environ is Linux specific */
-
- char temp[sizeof("/proc//environ") + 3 * sizeof(long long int)];
- int fd = -1, saved_errno, tries = 0;
- char* content = NULL;
- char* p;
- pid_t pid = 0;
- size_t n;
-#if defined(HAVE_LINUX_PROCFS)
- char* end;
-#else
- (void) token;
-#endif
-
- /* Get PID */
- retry:
- fd = open(pidpath, O_RDONLY);
- if (fd < 0)
- return -1;
- content = nread(fd, &n);
- if (content == NULL)
- goto fail;
- close(fd), fd = -1;
-
- if (n == 0)
- {
- if (++tries > 1)
- goto bad;
- msleep(100); /* 1 tenth of a second */
- goto retry;
- }
-
- if (('0' > content[0]) || (content[0] > '9'))
- goto bad;
- if ((content[0] == '0') && ('0' <= content[1]) && (content[1] <= '9'))
- goto bad;
- for (p = content; *p; p++)
- if (('0' <= *p) && (*p <= '9'))
- pid = pid * 10 + (*p & 15);
- else
- break;
- if (*p++ != '\n')
- goto bad;
- if (*p)
- goto bad;
- if ((size_t)(p - content) != n)
- goto bad;
- sprintf(temp, "%llu\n", (unsigned long long)pid);
- if (strcmp(content, temp))
- goto bad;
- free(content);
-
- /* Validate PID */
-#if defined(HAVE_LINUX_PROCFS)
- sprintf(temp, "/proc/%llu/environ", (unsigned long long)pid);
- fd = open(temp, O_RDONLY);
- if (fd < 0)
- return ((errno == ENOENT) || (errno == EACCES)) ? 1 : -1;
- content = nread(fd, &n);
- if (content == NULL)
- goto fail;
- close(fd), fd = -1;
-
- for (end = (p = content) + n; p != end; p = strchr(p, '\0') + 1)
- if (!strcmp(p, token))
- return free(content), 0;
- free(content);
-#else
- if ((kill(pid, 0) == 0) || (errno == EINVAL))
- return 0;
-#endif
-
- return 1;
- bad:
- fprintf(stderr, "%s: pid file contains invalid content: %s\n", argv0, pidpath);
- errno = 0;
- return -1;
- fail:
- saved_errno = errno;
- free(content);
- if (fd >= 0)
- close(fd);
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Create PID file
- *
- * @param pidpath The pathname of the PID file
- * @return Zero on success, -1 on error,
- * -2 if the service is already running
- */
-int create_pidfile(char* pidpath)
-{
- int fd = -1, r, saved_errno;
- char* p;
- char* restrict token = NULL;
-
- /* Create token used to validate the service. */
- token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath));
- if (token == NULL)
- return -1;
- sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath);
-#if !defined(USE_VALGRIND)
- if (putenv(token))
- goto putenv_fail;
- /* `token` must not be free! */
-#else
- {
- static char static_token[sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + PATH_MAX];
- if (strlen(pidpath) > PATH_MAX)
- abort();
- strcpy(static_token, token);
- if (putenv(static_token))
- goto fail;
- }
-#endif
-
- /* Create PID file's directory. */
- for (p = pidpath; *p == '/'; p++);
- while ((p = strchr(p, '/')))
- {
- *p = '\0';
- if (mkdir(pidpath, 0755) < 0)
- if (errno != EEXIST)
- {
- *p = '/';
- goto fail;
- }
- *p++ = '/';
- }
-
- /* Create PID file. */
- retry:
- fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644);
- if (fd < 0)
- {
- if (errno == EINTR)
- goto retry;
- if (errno != EEXIST)
- return -1;
- r = is_pidfile_reusable(pidpath, token);
- if (r > 0)
- {
- unlink(pidpath);
- goto retry;
- }
- else if (r < 0)
- goto fail;
- fprintf(stderr, "%s: service is already running\n", argv0);
- errno = 0;
- return -2;
- }
-
- /* Write PID to PID file. */
- if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0)
- goto fail;
-
- /* Done */
-#if defined(USE_VALGRIND)
- free(token);
-#endif
- if (close(fd) < 0)
- if (errno != EINTR)
- return -1;
- return 0;
-#if !defined(USE_VALGRIND)
- putenv_fail:
- saved_errno = errno;
- free(token);
- errno = saved_errno;
-#endif
- fail:
- saved_errno = errno;
-#if defined(USE_VALGRIND)
- free(token);
-#endif
- if (fd >= 0)
- {
- close(fd);
- unlink(pidpath);
- }
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Create socket and start listening
- *
- * @param socketpath The pathname of the socket
- * @return Zero on success, -1 on error
- */
-int create_socket(const char* socketpath)
-{
- struct sockaddr_un address;
-
- address.sun_family = AF_UNIX;
- if (strlen(socketpath) >= sizeof(address.sun_path))
- {
- errno = ENAMETOOLONG;
- return -1;
- }
- strcpy(address.sun_path, socketpath);
- unlink(socketpath);
- if ((socketfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
- return -1;
- if (fchmod(socketfd, S_IRWXU) < 0)
- return -1;
- if (bind(socketfd, (struct sockaddr*)(&address), (socklen_t)sizeof(address)) < 0)
- return -1;
- if (listen(socketfd, SOMAXCONN) < 0)
- return -1;
-
- return 0;
-}
-
-
-/**
- * Close and unlink the socket
- *
- * @param socketpath The pathname of the socket
- */
-void close_socket(const char* socketpath)
-{
- if (socketfd >= 0)
- {
- shutdown(socketfd, SHUT_RDWR);
- close(socketfd);
- unlink(socketpath);
- }
-}
-
diff --git a/src/servers/kernel.h b/src/servers/kernel.h
deleted file mode 100644
index 494e150..0000000
--- a/src/servers/kernel.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef SERVERS_KERNEL_H
-#define SERVERS_KERNEL_H
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Get the pathname of the socket
- *
- * @return The pathname of the socket, `NULL` on error
- */
-GCC_ONLY(__attribute__((malloc)))
-char* get_socket_pathname(void);
-
-/**
- * Get the pathname of the PID file
- *
- * @return The pathname of the PID file, `NULL` on error
- */
-GCC_ONLY(__attribute__((malloc)))
-char* get_pidfile_pathname(void);
-
-/**
- * Get the pathname of the state file
- *
- * @return The pathname of the state file, `NULL` on error
- */
-GCC_ONLY(__attribute__((malloc)))
-char* get_state_pathname(void);
-
-/**
- * Create PID file
- *
- * @param pidpath The pathname of the PID file
- * @return Zero on success, -1 on error,
- * -2 if the service is already running
- */
-GCC_ONLY(__attribute__((nonnull)))
-int create_pidfile(char* pidpath);
-
-/**
- * Create socket and start listening
- *
- * @param socketpath The pathname of the socket
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-int create_socket(const char* socketpath);
-
-/**
- * Close and unlink the socket
- *
- * @param socketpath The pathname of the socket
- */
-GCC_ONLY(__attribute__((nonnull)))
-void close_socket(const char* socketpath);
-
-
-#endif
-
diff --git a/src/servers/master.c b/src/servers/master.c
deleted file mode 100644
index 01b9bb5..0000000
--- a/src/servers/master.c
+++ /dev/null
@@ -1,394 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "master.h"
-#include "crtc.h"
-#include "gamma.h"
-#include "coopgamma.h"
-#include "../util.h"
-#include "../communication.h"
-#include "../state.h"
-
-#include <sys/socket.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-
-
-/**
- * All poll(3p) events that are not for writing
- */
-#define NON_WR_POLL_EVENTS (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL)
-
-
-/**
- * Extract headers from an inbound message and pass
- * them on to appropriate message handling function
- *
- * @param conn The index of the connection
- * @param msg The inbound message
- * @return 1: The connection as closed
- * 0: Successful
- * -1: Failure
- */
-static int dispatch_message(size_t conn, struct message* restrict msg)
-{
- size_t i;
- int r = 0;
- const char* header;
- const char* value;
- const char* command = NULL;
- const char* crtc = NULL;
- const char* coalesce = NULL;
- const char* high_priority = NULL;
- const char* low_priority = NULL;
- const char* priority = NULL;
- const char* class = NULL;
- const char* lifespan = NULL;
- const char* message_id = NULL;
-
- for (i = 0; i < msg->header_count; i++)
- {
- value = strstr((header = msg->headers[i]), ": ") + 2;
- if (strstr(header, "Command: ") == header) command = value;
- else if (strstr(header, "CRTC: ") == header) crtc = value;
- else if (strstr(header, "Coalesce: ") == header) coalesce = value;
- else if (strstr(header, "High priority: ") == header) high_priority = value;
- else if (strstr(header, "Low priority: ") == header) low_priority = value;
- else if (strstr(header, "Priority: ") == header) priority = value;
- else if (strstr(header, "Class: ") == header) class = value;
- else if (strstr(header, "Lifespan: ") == header) lifespan = value;
- else if (strstr(header, "Message ID: ") == header) message_id = value;
- else if (strstr(header, "Length: ") == header) ;/* Handled transparently */
- else
- fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header);
- }
-
- if (command == NULL)
- fprintf(stderr, "%s: ignoring message without Command header\n", argv0);
- else if (message_id == NULL)
- fprintf(stderr, "%s: ignoring message without Message ID header\n", argv0);
- else if (!strcmp(command, "enumerate-crtcs"))
- {
- if (crtc || coalesce || high_priority || low_priority || priority || class || lifespan)
- fprintf(stderr, "%s: ignoring superfluous headers in Command: enumerate-crtcs message\n", argv0);
- r = handle_enumerate_crtcs(conn, message_id);
- }
- else if (!strcmp(command, "get-gamma-info"))
- {
- if (coalesce || high_priority || low_priority || priority || class || lifespan)
- fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma-info message\n", argv0);
- r = handle_get_gamma_info(conn, message_id, crtc);
- }
- else if (!strcmp(command, "get-gamma"))
- {
- if (priority || class || lifespan)
- fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma message\n", argv0);
- r = handle_get_gamma(conn, message_id, crtc, coalesce, high_priority, low_priority);
- }
- else if (!strcmp(command, "set-gamma"))
- {
- if (coalesce || high_priority || low_priority)
- fprintf(stderr, "%s: ignoring superfluous headers in Command: set-gamma message\n", argv0);
- r = handle_set_gamma(conn, message_id, crtc, priority, class, lifespan);
- }
- else
- fprintf(stderr, "%s: ignoring unrecognised command: Command: %s\n", argv0, command);
-
- return r;
-}
-
-
-/**
- * Sets the file descriptor set that includes
- * the server socket and all connections
- *
- * The file descriptor will be ordered as in
- * the array `connections`, `socketfd` will
- * be last.
- *
- * @param fds Reference parameter for the array of file descriptors
- * @param fdn Output parameter for the number of file descriptors
- * @param fds_alloc Reference parameter for the allocation size of `fds`, in elements
- * @return Zero on success, -1 on error
- */
-static int update_fdset(struct pollfd** restrict fds, nfds_t* restrict fdn, nfds_t* restrict fds_alloc)
-{
- size_t i;
- nfds_t j = 0;
-
- if (connections_used + 1 > *fds_alloc)
- {
- void* new = realloc(*fds, (connections_used + 1) * sizeof(**fds));
- if (new == NULL)
- return -1;
- *fds = new;
- *fds_alloc = connections_used + 1;
- }
-
- for (i = 0; i < connections_used; i++)
- if (connections[i] >= 0)
- {
- (*fds)[j].fd = connections[i];
- (*fds)[j].events = NON_WR_POLL_EVENTS;
- j++;
- }
-
- (*fds)[j].fd = socketfd;
- (*fds)[j].events = NON_WR_POLL_EVENTS;
- j++;
-
- *fdn = j;
- return 0;
-}
-
-
-/**
- * Handle event on the server socket
- *
- * @return 1: New connection accepted
- * 0: Successful
- * -1: Failure
- */
-static int handle_server(void)
-{
- int fd, flags, saved_errno;
-
- fd = accept(socketfd, NULL, NULL);
- if (fd < 0)
- switch (errno)
- {
- case EINTR:
- return 0;
- case ECONNABORTED:
- case EINVAL:
- terminate = 1;
- return 0;
- default:
- return -1;
- }
-
- flags = fcntl(fd, F_GETFL);
- if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
- goto fail;
-
- if (connections_ptr == connections_alloc)
- {
- void* new;
-
- new = realloc(connections, (connections_alloc + 10) * sizeof(*connections));
- if (new == NULL)
- goto fail;
- connections = new;
- connections[connections_ptr] = fd;
-
- new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound));
- if (new == NULL)
- goto fail;
- outbound = new;
- ring_initialise(outbound + connections_ptr);
-
- new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound));
- if (new == NULL)
- goto fail;
- inbound = new;
- connections_alloc += 10;
- if (message_initialise(inbound + connections_ptr))
- goto fail;
- }
- else
- {
- connections[connections_ptr] = fd;
- ring_initialise(outbound + connections_ptr);
- if (message_initialise(inbound + connections_ptr))
- goto fail;
- }
-
- connections_ptr++;
- while ((connections_ptr < connections_used) && (connections[connections_ptr] >= 0))
- connections_ptr++;
- if (connections_used < connections_ptr)
- connections_used = connections_ptr;
-
- return 1;
- fail:
- saved_errno = errno;
- shutdown(fd, SHUT_RDWR);
- close(fd);
- errno = saved_errno;
- return -1;
-}
-
-
-/**
- * Handle event on a connection to a client
- *
- * @param conn The index of the connection
- * @return 1: The connection as closed
- * 0: Successful
- * -1: Failure
- */
-static int handle_connection(size_t conn)
-{
- struct message* restrict msg = inbound + conn;
- int r, fd = connections[conn];
-
- again:
- switch (message_read(msg, fd))
- {
- default:
- break;
- case -1:
- switch (errno)
- {
- case EINTR:
- case EAGAIN:
-#if EAGAIN != EWOULDBLOCK
- case EWOULDBLOCK:
-#endif
- return 0;
- default:
- return -1;
- case ECONNRESET:;
- /* Fall throught to `case -2` in outer switch */
- }
- case -2:
- shutdown(fd, SHUT_RDWR);
- close(fd);
- connections[conn] = -1;
- if (conn < connections_ptr)
- connections_ptr = conn;
- while ((connections_used > 0) && (connections[connections_used - 1] < 0))
- connections_used -= 1;
- message_destroy(msg);
- ring_destroy(outbound + conn);
- if (connection_closed(fd) < 0)
- return -1;
- return 1;
- }
-
- if ((r = dispatch_message(conn, msg)))
- return r;
-
- goto again;
-}
-
-
-
-/**
- * Disconnect all clients
- */
-void disconnect_all(void)
-{
- size_t i;
- for (i = 0; i < connections_used; i++)
- if (connections[i] >= 0)
- {
- shutdown(connections[i], SHUT_RDWR);
- close(connections[i]);
- }
-}
-
-
-/**
- * The program's main loop
- *
- * @return Zero on success, -1 on error
- */
-int main_loop(void)
-{
- struct pollfd* fds = NULL;
- nfds_t i, fdn = 0, fds_alloc = 0;
- int r, update, saved_errno;
- size_t j;
-
- if (update_fdset(&fds, &fdn, &fds_alloc) < 0)
- goto fail;
-
- while (!reexec && !terminate)
- {
- if (connection)
- {
- if ((connection == 1 ? disconnect() : reconnect()) < 0)
- {
- connection = 0;
- goto fail;
- }
- connection = 0;
- }
-
- for (j = 0, i = 0; j < connections_used; j++)
- if (connections[j] >= 0)
- {
- fds[i].revents = 0;
- if (ring_have_more(outbound + j))
- fds[(size_t)i++ + j].events |= POLLOUT;
- else
- fds[(size_t)i++ + j].events &= ~POLLOUT;
- }
- fds[i].revents = 0;
-
- if (poll(fds, fdn, -1) < 0)
- {
- if (errno == EAGAIN)
- perror(argv0);
- else if (errno != EINTR)
- goto fail;
- }
-
- update = 0;
- for (i = 0; i < fdn; i++)
- {
- int do_read = fds[i].revents & NON_WR_POLL_EVENTS;
- int do_write = fds[i].revents & POLLOUT;
- int fd = fds[i].fd;
- if (!do_read && !do_write)
- continue;
-
- if (fd == socketfd)
- r = handle_server();
- else
- {
- for (j = 0; connections[j] != fd; j++);
- r = do_read ? handle_connection(j) : 0;
- }
-
- if ((r >= 0) && do_write)
- r |= continue_send(j);
- if (r < 0)
- goto fail;
- update |= (r > 0);
- }
- if (update)
- if (update_fdset(&fds, &fdn, &fds_alloc) < 0)
- goto fail;
- }
-
- free(fds);
- return 0;
- fail:
- saved_errno = errno;
- free(fds);
- errno = saved_errno;
- return -1;
-}
-
diff --git a/src/servers/master.h b/src/servers/master.h
deleted file mode 100644
index d7ef6e5..0000000
--- a/src/servers/master.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef SERVERS_MASTER_H
-#define SERVERS_MASTER_H
-
-
-/**
- * Disconnect all clients
- */
-void disconnect_all(void);
-
-/**
- * The program's main loop
- *
- * @return Zero on success, -1 on error
- */
-int main_loop(void);
-
-
-#endif
-
diff --git a/src/state.c b/src/state.c
deleted file mode 100644
index ff4f0e9..0000000
--- a/src/state.c
+++ /dev/null
@@ -1,638 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "state.h"
-#include "util.h"
-
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * The name of the process
- */
-char* restrict argv0; /* do not marshal */
-
-/**
- * The real pathname of the process's binary,
- * `NULL` if `argv0` is satisfactory
- */
-char* restrict argv0_real = NULL;
-
-/**
- * Array of all outputs
- */
-struct output* restrict outputs = NULL;
-
-/**
- * The nubmer of elements in `outputs`
- */
-size_t outputs_n = 0;
-
-/**
- * The server socket's file descriptor
- */
-int socketfd = -1;
-
-/**
- * Has the process receive a signal
- * telling it to re-execute?
- */
-volatile sig_atomic_t reexec = 0; /* do not marshal */
-
-/**
- * Has the process receive a signal
- * telling it to terminate?
- */
-volatile sig_atomic_t terminate = 0; /* do not marshal */
-
-/**
- * Has the process receive a to
- * disconnect from or reconnect to
- * the site? 1 if disconnect, 2 if
- * reconnect, 0 otherwise.
- */
-volatile sig_atomic_t connection = 0;
-
-/**
- * List of all client's file descriptors
- *
- * Unused slots, with index less than `connections_used`,
- * should have the value -1 (negative)
- */
-int* restrict connections = NULL;
-
-/**
- * The number of elements allocated for `connections`
- */
-size_t connections_alloc = 0;
-
-/**
- * The index of the first unused slot in `connections`
- */
-size_t connections_ptr = 0;
-
-/**
- * The index of the last used slot in `connections`, plus 1
- */
-size_t connections_used = 0;
-
-/**
- * The clients' connections' inbound-message buffers
- */
-struct message* restrict inbound = NULL;
-
-/**
- * The clients' connections' outbound-message buffers
- */
-struct ring* restrict outbound = NULL;
-
-/**
- * Is the server connect to the display?
- *
- * Set to true before the initial connection
- */
-int connected = 1;
-
-/**
- * The adjustment method, -1 for automatic
- */
-int method = -1;
-
-/**
- * The site's name, may be `NULL`
- */
-char* restrict sitename = NULL;
-
-/**
- * The libgamma site state
- */
-libgamma_site_state_t site; /* do not marshal */
-
-/**
- * The libgamma partition states
- */
-libgamma_partition_state_t* restrict partitions = NULL; /* do not marshal */
-
-/**
- * The libgamma CRTC states
- */
-libgamma_crtc_state_t* restrict crtcs = NULL; /* do not marshal */
-
-/**
- * Preserve gamma ramps at priority 0?
- */
-int preserve = 0;
-
-
-
-/**
- * As part of a state dump, dump one or two gamma ramp-trios
- *
- * @param left The left ramps
- * @param right The right ramps
- * @param depth The gamma ramp type/depth
- * @param have_right Print right ramps?
- * @param indent Print indent
- */
-static void ramps_dump(union gamma_ramps* left, union gamma_ramps* right,
- signed depth, int have_right, const char* indent)
-{
-#define STRINGISE(SIDE, CH, N, BUF) \
- do \
- if ((SIDE == NULL) || (SIDE->u8.CH == NULL)) \
- strcpy(BUF, "null"); \
- else if (i < N) \
- switch (depth) \
- { \
- case -2: snprintf(BUF, sizeof(BUF), "%lf", SIDE->d.CH[i]); break; \
- case -1: snprintf(BUF, sizeof(BUF), "%f", (double)(SIDE->f.CH[i])); break; \
- case 8: snprintf(BUF, sizeof(BUF), "%02" PRIx8, SIDE->u8.CH[i]); break; \
- case 16: snprintf(BUF, sizeof(BUF), "%04" PRIx16, SIDE->u16.CH[i]); break; \
- case 32: snprintf(BUF, sizeof(BUF), "%08" PRIx32, SIDE->u32.CH[i]); break; \
- case 64: snprintf(BUF, sizeof(BUF), "%16" PRIx64, SIDE->u64.CH[i]); break; \
- default: \
- strcpy(BUF, "corrupt state"); \
- break; \
- } \
- while (0)
-
- char lr[100], lg[100], lb[100], rr[100], rg[100], rb[100];
- size_t rn = left ? left->u8.red_size : right ? right->u8.red_size : 0;
- size_t gn = left ? left->u8.green_size : right ? right->u8.green_size : 0;
- size_t bn = left ? left->u8.blue_size : right ? right->u8.blue_size : 0;
- size_t i, n = rn > gn ? rn : gn;
- n = n > bn ? n : bn;
-
- for (i = 0; i < n; i++)
- {
- *lr = *lg = *lb = *rr = *rg = *rb = '\0';
-
- STRINGISE(left, red, rn, lr);
- STRINGISE(left, green, gn, lg);
- STRINGISE(left, blue, bn, lb);
-
- if (have_right)
- {
- STRINGISE(right, red, rn, rr);
- STRINGISE(right, green, gn, rg);
- STRINGISE(right, blue, bn, rb);
- }
-
- if (have_right)
- fprintf(stderr, "%s%zu: %s, %s, %s :: %s, %s, %s\n", indent, i, lr, lg, lb, rr, rg, rb);
- else
- fprintf(stderr, "%s%zu: %s, %s, %s\n", indent, i, lr, lg, lb);
- }
-}
-
-
-/**
- * Dump the state to stderr
- */
-void state_dump(void)
-{
- size_t i, j;
- fprintf(stderr, "argv0: %s\n", argv0 ? argv0 : "(null)");
- fprintf(stderr, "Realpath of argv0: %s\n", argv0_real ? argv0_real : "(null)");
- fprintf(stderr, "Calibrations preserved: %s\n", preserve ? "yes" : "no");
- fprintf(stderr, "Connected: %s\n", connected ? "yes" : "no");
- fprintf(stderr, "Socket FD: %i\n", socketfd);
- fprintf(stderr, "Re-execution pending: %s\n", reexec ? "yes" : "no");
- fprintf(stderr, "Termination pending: %s\n", terminate ? "yes" : "no");
- if ((0 <= connection) && (connection <= 2))
- fprintf(stderr, "Pending connection change: %s\n",
- connection == 0 ? "none" : connection == 1 ? "disconnect" : "reconnect");
- else
- fprintf(stderr, "Pending connection change: %i (CORRUPT STATE)\n", connection);
- fprintf(stderr, "Adjustment method: %i\n", method);
- fprintf(stderr, "Site name: %s\n", sitename ? sitename : "(automatic)");
- fprintf(stderr, "Clients:\n");
- fprintf(stderr, " Next empty slot: %zu\n", connections_ptr);
- fprintf(stderr, " Initialised slots: %zu\n", connections_used);
- fprintf(stderr, " Allocated slots: %zu\n", connections_alloc);
- if (connections == NULL)
- fprintf(stderr, " File descriptor array is null\n");
- else
- for (i = 0; i < connections_used; i++)
- {
- if (connections[i] < 0)
- {
- fprintf(stderr, " Slot %zu: empty\n", i);
- continue;
- }
- fprintf(stderr, " Slot %zu:\n", i);
- fprintf(stderr, " File descriptor: %i\n", connections[i]);
- if (inbound == NULL)
- fprintf(stderr, " Inbound message array is null\n");
- else
- {
- fprintf(stderr, " Inbound message:\n");
- fprintf(stderr, " Header array: %s\n", inbound[i].headers ? "non-null" : "null");
- fprintf(stderr, " Headers: %zu\n", inbound[i].header_count);
- fprintf(stderr, " Payload buffer: %s\n", inbound[i].payload ? "non-null" : "null");
- fprintf(stderr, " Payload size: %zu\n", inbound[i].payload_size);
- fprintf(stderr, " Payload write pointer: %zu\n", inbound[i].payload_ptr);
- fprintf(stderr, " Message buffer: %s\n", inbound[i].buffer ? "non-null" : "null");
- fprintf(stderr, " Message buffer size: %zu\n", inbound[i].buffer_size);
- fprintf(stderr, " Message buffer write pointer: %zu\n", inbound[i].buffer_ptr);
- fprintf(stderr, " Read stage: %i\n", inbound[i].stage);
- }
- if (outbound == NULL)
- fprintf(stderr, " Outbound message array is null\n");
- else
- {
- fprintf(stderr, " Ring buffer: %s\n", outbound[i].buffer ? "non-null" : "null");
- fprintf(stderr, " Head: %zu\n", outbound[i].end);
- fprintf(stderr, " Tail: %zu\n", outbound[i].start);
- fprintf(stderr, " Size: %zu\n", outbound[i].size);
- }
- }
- fprintf(stderr, "Partition array: %s\n", partitions ? "non-null" : "null");
- fprintf(stderr, "CRTC array: %s\n", crtcs ? "non-null" : "null");
- fprintf(stderr, "Output:\n");
- fprintf(stderr, " Output count: %zu\n", outputs_n);
- if (outputs == NULL)
- fprintf(stderr, " Output array is null\n");
- else
- for (i = 0; i < outputs_n; i++)
- {
- struct output* restrict out = outputs + i;
- const char* str;
- fprintf(stderr, " Output %zu:\n", i);
- fprintf(stderr, " Depth: %i (%s)\n", out->depth,
- out->depth == -1 ? "float" :
- out->depth == -2 ? "double" :
- out->depth == 8 ? "uint8_t" :
- out->depth == 16 ? "uint16_t" :
- out->depth == 32 ? "uint32_t" :
- out->depth == 64 ? "uint64_t" : "CORRUPT STATE");
- fprintf(stderr, " Gamma supported: %s (%i)\n",
- out->supported == LIBGAMMA_YES ? "yes" :
- out->supported == LIBGAMMA_NO ? "no" :
- out->supported == LIBGAMMA_MAYBE ? "maybe" :
- "CORRUPT STATE", out->supported);
- fprintf(stderr, " Name is EDID: %s\n", out->name_is_edid ? "yes" : "no");
- switch (out->colourspace)
- {
- case COLOURSPACE_UNKNOWN: str = "unknown"; break;
- case COLOURSPACE_SRGB: str = "sRGB with explicit gamut"; break;
- case COLOURSPACE_SRGB_SANS_GAMUT: str = "sRGB with implicit gamut (actually illegal)"; break;
- case COLOURSPACE_RGB: str = "RGB other than sRGB, with unknown gamut"; break;
- case COLOURSPACE_RGB_SANS_GAMUT: str = "RGB other than sRGB, with listed gamut"; break;
- case COLOURSPACE_NON_RGB: str = "Non-RGB multicolour"; break;
- case COLOURSPACE_GREY: str = "Monochrome or singlecolour scale"; break;
- default: str = "CORRUPT STATE"; break;
- }
- fprintf(stderr, " Colourspace: %s (%i)\n", str, out->colourspace);
- if ((out->colourspace == COLOURSPACE_SRGB) || (out->colourspace == COLOURSPACE_RGB))
- {
- fprintf(stderr, " Red (x, y): (%u / 1024, %u / 1024)\n", out->red_x, out->red_y);
- fprintf(stderr, " Green (x, y): (%u / 1024, %u / 1024)\n", out->green_x, out->green_y);
- fprintf(stderr, " Blue (x, y): (%u / 1024, %u / 1024)\n", out->blue_x, out->blue_y);
- fprintf(stderr, " White (x, y): (%u / 1024, %u / 1024)\n", out->white_x, out->white_y);
- if (out->colourspace == COLOURSPACE_SRGB)
- {
- fprintf(stderr, " Expected red (x, y): (655 / 1024, 338 / 1024)\n");
- fprintf(stderr, " Expected green (x, y): (307 / 1024, 614 / 1024)\n");
- fprintf(stderr, " Expected blue (x, y): (154 / 1024, 61 / 1024)\n");
- fprintf(stderr, " Expected white (x, y): (320 / 1024, 337 / 1024)\n");
- }
- }
- if (out->supported)
- {
- fprintf(stderr, " Gamma ramp size:\n");
- fprintf(stderr, " Red: %zu stops\n", out->red_size);
- fprintf(stderr, " Green: %zu stops\n", out->green_size);
- fprintf(stderr, " Blue: %zu stops\n", out->blue_size);
- fprintf(stderr, " Total: %zu bytes\n", out->ramps_size);
- fprintf(stderr, " Name: %s\n", out->name ? out->name : "(null)");
- fprintf(stderr, " CRTC state: %s\n", out->crtc ? "non-null" : "null");
- fprintf(stderr, " Saved gamma ramps (stop: red, green, blue):\n");
- ramps_dump(&(out->saved_ramps), NULL, out->depth, 0, " ");
- fprintf(stderr, " Filter table:\n");
- fprintf(stderr, " Filter count: %zu\n", out->table_size);
- fprintf(stderr, " Slots allocated: %zu\n", out->table_alloc);
- if (out->table_size > 0)
- {
- if (out->table_filters == NULL)
- fprintf(stderr, " Filter table is null\n");
- if (out->table_sums == NULL)
- fprintf(stderr, " Result table is null\n");
- }
- for (j = 0; j < out->table_size; j++)
- {
- struct filter* restrict filter = out->table_filters ? out->table_filters + j : NULL;
- fprintf(stderr, " Filter %zu:\n", j);
- if (filter != NULL)
- {
- if (filter->lifespan == LIFESPAN_UNTIL_DEATH)
- fprintf(stderr, " Client FD: %i\n", filter->client);
- switch (filter->lifespan)
- {
- case LIFESPAN_REMOVE: str = "remove (ILLEGAL STATE)"; break;
- case LIFESPAN_UNTIL_REMOVAL: str = "until-removal"; break;
- case LIFESPAN_UNTIL_DEATH: str = "until-death"; break;
- default: str = "CORRUPT STATE"; break;
- }
- fprintf(stderr, " Lifespan: %s (%i)\n", str, filter->lifespan);
- fprintf(stderr, " Priority: %"PRIi64"\n", filter->priority);
- fprintf(stderr, " Class: %s\n", filter->class ? filter->class : "(null)");
- str = "yes";
- if (filter->class == NULL)
- str = "no, is NULL";
- else if (strchr(filter->class, '\n'))
- str = "no, contains LF";
- else if (strstr(filter->class, "::") == NULL)
- str = "no, does not contain \"::\"";
- else if (strstr(strstr(filter->class, "::") + 2, "::") == NULL)
- str = "no, contains only one \"::\"";
- else if (verify_utf8(filter->class) < 0)
- str = "no, not UTF-8";
- fprintf(stderr, " Class legal: %s\n", str);
- if ((filter->ramps == NULL) && (filter->lifespan != LIFESPAN_REMOVE))
- fprintf(stderr, " Ramps are NULL\n");
- }
- if (filter != NULL ? (filter->lifespan != LIFESPAN_REMOVE) : (out->table_sums != NULL))
- {
- union gamma_ramps left;
- size_t depth;
- switch (out->depth)
- {
- case -2: depth = sizeof(double); break;
- case -1: depth = sizeof(float); break;
- case 8: case 16: case 32: case 64:
- depth = (size_t)(out->depth) / 8;
- break;
- default:
- goto corrupt_depth;
- }
- if (filter && filter->ramps)
- {
- left.u8.red_size = out->red_size;
- left.u8.green_size = out->green_size;
- left.u8.blue_size = out->blue_size;
- left.u8.red = filter->ramps;
- left.u8.green = left.u8.red + out->red_size * depth;
- left.u8.blue = left.u8.green + out->green_size * depth;
- }
- fprintf(stderr," Ramps (stop: filter red, green, blue :: "
- "composite red, geen, blue):\n");
- ramps_dump((filter && filter->ramps) ? &left : NULL,
- out->table_sums ? out->table_sums + j : NULL,
- out->depth, 1, " ");
- corrupt_depth:;
- }
- }
- }
- }
-}
-
-
-/**
- * Destroy the state
- */
-void state_destroy(void)
-{
- size_t i;
-
- for (i = 0; i < connections_used; i++)
- if (connections[i] >= 0)
- {
- message_destroy(inbound + i);
- ring_destroy(outbound + i);
- }
- free(inbound);
- free(outbound);
- free(connections);
-
- if (outputs != NULL)
- for (i = 0; i < outputs_n; i++)
- output_destroy(outputs + i);
- free(outputs);
- if (crtcs != NULL)
- for (i = 0; i < outputs_n; i++)
- libgamma_crtc_destroy(crtcs + i);
- free(crtcs);
- if (partitions != NULL)
- for (i = 0; i < site.partitions_available; i++)
- libgamma_partition_destroy(partitions + i);
- free(partitions);
- libgamma_site_destroy(&site);
-
- free(sitename);
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal the state
- *
- * @param buf Output buffer for the marshalled data,
- * `NULL` to only measure how many bytes
- * this buffer needs
- * @return The number of marshalled bytes
- */
-size_t state_marshal(void* restrict buf)
-{
- size_t off = 0, i, n;
- char* restrict bs = buf;
-
- if (argv0_real == NULL)
- {
- if (bs != NULL)
- *(bs + off) = '\0';
- off += 1;
- }
- else
- {
- n = strlen(argv0_real) + 1;
- if (bs != NULL)
- memcpy(bs + off, argv0_real, n);
- off += n;
- }
-
- if (bs != NULL)
- *(size_t*)(bs + off) = outputs_n;
- off += sizeof(size_t);
-
- for (i = 0; i < outputs_n; i++)
- off += output_marshal(outputs + i, bs ? bs + off : NULL);
-
- if (bs != NULL)
- *(int*)(bs + off) = socketfd;
- off += sizeof(int);
-
- if (bs != NULL)
- *(sig_atomic_t*)(bs + off) = connection;
- off += sizeof(sig_atomic_t);
-
- if (bs != NULL)
- *(int*)(bs + off) = connected;
- off += sizeof(int);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = connections_ptr;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = connections_used;
- off += sizeof(size_t);
-
- if (bs != NULL)
- memcpy(bs + off, connections, connections_used * sizeof(*connections));
- off += connections_used * sizeof(*connections);
-
- for (i = 0; i < connections_used; i++)
- if (connections[i] >= 0)
- {
- off += message_marshal(inbound + i, bs ? bs + off : NULL);
- off += ring_marshal(outbound + i, bs ? bs + off : NULL);
- }
-
- if (bs != NULL)
- *(int*)(bs + off) = method;
- off += sizeof(int);
-
- if (bs != NULL)
- *(int*)(bs + off) = sitename != NULL;
- off += sizeof(int);
- if (sitename != NULL)
- {
- n = strlen(sitename) + 1;
- if (bs != NULL)
- memcpy(bs + off, sitename, n);
- off += n;
- }
-
- if (bs != NULL)
- *(int*)(bs + off) = preserve;
- off += sizeof(int);
-
- return off;
-}
-
-
-/**
- * Unmarshal the state
- *
- * @param buf Buffer for the marshalled data
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t state_unmarshal(const void* restrict buf)
-{
- size_t off = 0, i, n;
- const char* restrict bs = buf;
-
- connections = NULL;
- inbound = NULL;
-
- if (*(bs + off))
- {
- n = strlen(bs + off) + 1;
- if (!(argv0_real = memdup(bs + off, n)))
- return 0;
- off += n;
- }
- else
- off += 1;
-
- outputs_n = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- outputs = calloc(outputs_n, sizeof(*outputs));
- if (outputs == NULL)
- return 0;
-
- for (i = 0; i < outputs_n; i++)
- {
- off += n = output_unmarshal(outputs + i, bs + off);
- if (n == 0)
- return 0;
- }
-
- socketfd = *(const int*)(bs + off);
- off += sizeof(int);
-
- connection = *(const sig_atomic_t*)(bs + off);
- off += sizeof(sig_atomic_t);
-
- connected = *(const int*)(bs + off);
- off += sizeof(int);
-
- connections_ptr = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- connections_alloc = connections_used = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- if (connections_alloc > 0)
- {
- connections = memdup(bs + off, connections_alloc * sizeof(*connections));
- if (connections == NULL)
- return 0;
- off += connections_used * sizeof(*connections);
-
- inbound = malloc(connections_alloc * sizeof(*inbound));
- if (inbound == NULL)
- return 0;
- }
-
- for (i = 0; i < connections_used; i++)
- if (connections[i] >= 0)
- {
- off += n = message_unmarshal(inbound + i, bs + off);
- if (n == 0)
- return 0;
- off += n = ring_unmarshal(outbound + i, bs + off);
- if (n == 0)
- return 0;
- }
-
- method = *(const int*)(bs + off);
- off += sizeof(int);
-
- if (*(const int*)(bs + off))
- {
- off += sizeof(int);
- n = strlen(bs + off) + 1;
- if (!(sitename = memdup(bs + off, n)))
- return 0;
- off += n;
- }
- else
- off += sizeof(int);
-
- preserve = *(const int*)(bs + off);
- off += sizeof(int);
-
- return off;
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
-
diff --git a/src/types/filter.c b/src/types/filter.c
deleted file mode 100644
index e6facc9..0000000
--- a/src/types/filter.c
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "filter.h"
-#include "../util.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * Free all resources allocated to a filter.
- * The allocation of `filter` itself is not freed.
- *
- * @param this The filter
- */
-void filter_destroy(struct filter* restrict this)
-{
- free(this->class);
- free(this->ramps);
-}
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal a filter
- *
- * @param this The filter
- * @param buf Output buffer for the marshalled filter,
- * `NULL` just measure how large the buffers
- * needs to be
- * @param ramps_size The byte-size of `this->ramps`
- * @return The number of marshalled byte
- */
-size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size)
-{
- size_t off = 0, n;
- char nonnulls = 0;
- char* restrict bs = buf;
-
- if (bs != NULL)
- {
- if (this->class != NULL) nonnulls |= 1;
- if (this->ramps != NULL) nonnulls |= 2;
- *(bs + off) = nonnulls;
- }
- off += 1;
-
- if (bs != NULL)
- *(int64_t*)(bs + off) = this->priority;
- off += sizeof(int64_t);
-
- if (bs != NULL)
- *(enum lifespan*)(bs + off) = this->lifespan;
- off += sizeof(enum lifespan);
-
- if (this->class != NULL)
- {
- n = strlen(this->class) + 1;
- if (bs != NULL)
- memcpy(bs + off, this->class, n);
- off += n;
- }
-
- if (this->ramps != NULL)
- {
- if (bs != NULL)
- memcpy(bs + off, this->ramps, ramps_size);
- off += ramps_size;
- }
-
- return off;
-}
-
-
-/**
- * Unmarshal a filter
- *
- * @param this Output for the filter
- * @param buf Buffer with the marshalled filter
- * @param ramps_size The byte-size of `this->ramps`
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size)
-{
- size_t off = 0, n;
- char nonnulls = 0;
- const char* restrict bs = buf;
-
- nonnulls = *(bs + off);
- off += 1;
-
- this->class = NULL;
- this->ramps = NULL;
-
- this->priority = *(const int64_t*)(bs + off);
- off += sizeof(int64_t);
-
- this->lifespan = *(const enum lifespan*)(bs + off);
- off += sizeof(enum lifespan);
-
- if (nonnulls & 1)
- {
- n = strlen(bs + off) + 1;
- if (!(this->class = memdup(bs + off, n)))
- goto fail;
- off += n;
- }
-
- if (nonnulls & 2)
- {
- if (!(this->ramps = memdup(bs + off, ramps_size)))
- goto fail;
- off += ramps_size;
- }
-
- return off;
-
- fail:
- free(this->class);
- free(this->ramps);
- return 0;
-}
-
diff --git a/src/types/filter.h b/src/types/filter.h
deleted file mode 100644
index c91ccb2..0000000
--- a/src/types/filter.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef TYPES_FILTER_H
-#define TYPES_FILTER_H
-
-
-#include <stddef.h>
-#include <stdint.h>
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * The lifespan of a filter
- */
-enum lifespan
-{
- /**
- * The filter should be removed now
- */
- LIFESPAN_REMOVE = 0,
-
- /**
- * The filter should be applied
- * until it is explicitly removed
- */
- LIFESPAN_UNTIL_REMOVAL = 1,
-
- /**
- * The filter should be applied
- * until the client exists
- */
- LIFESPAN_UNTIL_DEATH = 2
-
-};
-
-
-/**
- * Information about a filter
- */
-struct filter
-{
- /**
- * The client that applied it. This need not be
- * set unless `.lifespan == LIFESPAN_UNTIL_DEATH`
- * and unless the process itself added this.
- * This is the file descriptor of the client's
- * connection.
- */
- int client;
-
- /**
- * The lifespan of the filter
- */
- enum lifespan lifespan;
-
- /**
- * The priority of the filter
- */
- int64_t priority;
-
- /**
- * Identifier for the filter
- */
- char* class;
-
- /**
- * The gamma ramp adjustments for the filter.
- * This is raw binary data. `NULL` iff
- * `lifespan == LIFESPAN_REMOVE`.
- */
- void* ramps;
-
-};
-
-
-
-/**
- * Free all resources allocated to a filter.
- * The allocation of `filter` itself is not freed.
- *
- * @param this The filter
- */
-GCC_ONLY(__attribute__((nonnull)))
-void filter_destroy(struct filter* restrict this);
-
-/**
- * Marshal a filter
- *
- * @param this The filter
- * @param buf Output buffer for the marshalled filter,
- * `NULL` just measure how large the buffers
- * needs to be
- * @param ramps_size The byte-size of `filter->ramps`
- * @return The number of marshalled byte
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size);
-
-/**
- * Unmarshal a filter
- *
- * @param this Output for the filter, `.red_size`, `.green_size`,
- * and `.blue_size` must already be set
- * @param buf Buffer with the marshalled filter
- * @param ramps_size The byte-size of `filter->ramps`
- * @return The number of unmarshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size);
-
-
-#endif
-
diff --git a/src/types/message.c b/src/types/message.c
deleted file mode 100644
index b0a6469..0000000
--- a/src/types/message.c
+++ /dev/null
@@ -1,572 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "message.h"
-#include "../util.h"
-
-#include <sys/socket.h>
-#include <errno.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * Initialise a message slot so that it can
- * be used by to read messages
- *
- * @param this Memory slot in which to store the new message
- * @return Non-zero on error, `errno` will be set accordingly
- */
-int message_initialise(struct message* restrict this)
-{
- this->headers = NULL;
- this->header_count = 0;
- this->payload = NULL;
- this->payload_size = 0;
- this->payload_ptr = 0;
- this->buffer_size = 128;
- this->buffer_ptr = 0;
- this->stage = 0;
- this->buffer = malloc(this->buffer_size);
- if (this->buffer == NULL)
- return -1;
- return 0;
-}
-
-
-/**
- * Release all resources in a message, should
- * be done even if initialisation fails
- *
- * @param this The message
- */
-void message_destroy(struct message* restrict this)
-{
- size_t i;
- if (this->headers != NULL)
- for (i = 0; i < this->header_count; i++)
- free(this->headers[i]);
-
- free(this->headers);
- free(this->payload);
- free(this->buffer);
-}
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal a message for state serialisation
- *
- * @param this The message
- * @param buf Output buffer for the marshalled data,
- * `NULL` just measure how large the buffers
- * needs to be
- * @return The number of marshalled byte
- */
-size_t message_marshal(const struct message* restrict this, void* restrict buf)
-{
- size_t i, n, off = 0;
- char* bs = buf;
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->header_count;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->payload_size;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->payload_ptr;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->buffer_ptr;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(int*)(bs + off) = this->stage;
- off += sizeof(int);
-
- for (i = 0; i < this->header_count; i++)
- {
- n = strlen(this->headers[i]) + 1;
- if (bs != NULL)
- memcpy(bs + off, this->headers[i], n);
- off += n;
- }
-
- if (bs != NULL)
- memcpy(bs + off, this->payload, this->payload_ptr);
- off += this->payload_ptr;
-
- if (bs != NULL)
- memcpy(bs + off, this->buffer, this->buffer_ptr);
- off += this->buffer_ptr;
-
- return off;
-}
-
-
-/**
- * Unmarshal a message for state deserialisation
- *
- * @param this Memory slot in which to store the new message
- * @param buf In buffer with the marshalled data
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t message_unmarshal(struct message* restrict this, const void* restrict buf)
-{
- size_t i, n, off = 0, header_count;
- const char* bs = buf;
-
- this->header_count = 0;
-
- header_count = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->payload_size = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->payload_ptr = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->buffer_size = this->buffer_ptr = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->stage = *(const int*)(bs + off);
- off += sizeof(int);
-
- /* Make sure that the pointers are NULL so that they are
- not freed without being allocated when the message is
- destroyed if this function fails. */
- this->headers = NULL;
- this->payload = NULL;
- this->buffer = NULL;
-
- /* To 2-power-multiple of 128 bytes. */
- this->buffer_size >>= 7;
- if (this->buffer_size == 0)
- this->buffer_size = 1;
- else
- {
- this->buffer_size -= 1;
- this->buffer_size |= this->buffer_size >> 1;
- this->buffer_size |= this->buffer_size >> 2;
- this->buffer_size |= this->buffer_size >> 4;
- this->buffer_size |= this->buffer_size >> 8;
- this->buffer_size |= this->buffer_size >> 16;
-#if SIZE_MAX == UINT64_MAX
- this->buffer_size |= this->buffer_size >> 32;
-#endif
- this->buffer_size += 1;
- }
- this->buffer_size <<= 7;
-
- /* Allocate header list, payload and read buffer. */
-
- if (header_count > 0)
- if (!(this->headers = malloc(header_count * sizeof(char*))))
- goto fail;
-
- if (this->payload_size > 0)
- if (!(this->payload = malloc(this->payload_size)))
- goto fail;
-
- if (!(this->buffer = malloc(this->buffer_size)))
- goto fail;
-
- /* Fill the header list, payload and read buffer. */
-
- for (i = 0; i < header_count; i++)
- {
- n = strlen(bs + off) + 1;
- this->headers[i] = memdup(bs + off, n);
- if (this->headers[i] == NULL)
- goto fail;
- off += n;
- this->header_count++;
- }
-
- memcpy(this->payload, bs + off, this->payload_ptr);
- off += this->payload_ptr;
-
- memcpy(this->buffer, bs + off, this->buffer_ptr);
- off += this->buffer_ptr;
-
- return off;
-
- fail:
- return 0;
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
-
-
-
-/**
- * Extend the header list's allocation
- *
- * @param this The message
- * @param extent The number of additional entries
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int extend_headers(struct message* restrict this, size_t extent)
-{
- char** new;
- if (!(new = realloc(this->headers, (this->header_count + extent) * sizeof(char*))))
- return -1;
- this->headers = new;
- return 0;
-}
-
-
-/**
- * Extend the read buffer by way of doubling
- *
- * @param this The message
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int extend_buffer(struct message* restrict this)
-{
- char* restrict new;
- if (!(new = realloc(this->buffer, (this->buffer_size << 1) * sizeof(char))))
- return -1;
- this->buffer = new;
- this->buffer_size <<= 1;
- return 0;
-}
-
-
-/**
- * Reset the header list and the payload
- *
- * @param this The message
- */
-GCC_ONLY(__attribute__((nonnull)))
-static void reset_message(struct message* restrict this)
-{
- size_t i;
- if (this->headers != NULL)
- for (i = 0; i < this->header_count; i++)
- free(this->headers[i]);
- free(this->headers);
- this->headers = NULL;
- this->header_count = 0;
-
- free(this->payload);
- this->payload = NULL;
- this->payload_size = 0;
- this->payload_ptr = 0;
-}
-
-
-/**
- * Read the headers the message and determine, and store, its payload's length
- *
- * @param this The message
- * @return Zero on success, negative on error (malformated message: unrecoverable state)
- */
-GCC_ONLY(__attribute__((pure, nonnull)))
-static int get_payload_length(struct message* restrict this)
-{
- char* header;
- size_t i;
-
- for (i = 0; i < this->header_count; i++)
- if (strstr(this->headers[i], "Length: ") == this->headers[i])
- {
- /* Store the message length. */
- header = this->headers[i] + strlen("Length: ");
- this->payload_size = (size_t)atol(header);
-
- /* Do not except a length that is not correctly formated. */
- for (; *header; header++)
- if ((*header < '0') || ('9' < *header))
- return -2; /* Malformated value, enters unrecoverable state. */
-
- /* Stop searching for the ‘Length’ header, we have found and parsed it. */
- break;
- }
-
- return 0;
-}
-
-
-/**
- * Verify that a header is correctly formatted
- *
- * @param header The header, must be NUL-terminated
- * @param length The length of the header
- * @return Zero if valid, negative if invalid (malformated message: unrecoverable state)
- */
-GCC_ONLY(__attribute__((pure, nonnull)))
-static int validate_header(const char* restrict header, size_t length)
-{
- char* restrict p = memchr(header, ':', length * sizeof(char));
-
- if (verify_utf8(header) < 0)
- /* Either the string is not UTF-8, or your are under an UTF-8 attack,
- let's just call this unrecoverable because the client will not correct. */
- return -2;
-
- if ((p == NULL) || /* Buck you, rawmemchr should not segfault the program. */
- (p[1] != ' ')) /* Also an invalid format. ' ' is mandated after the ':'. */
- return -2;
-
- return 0;
-}
-
-
-/**
- * Remove the beginning of the read buffer
- *
- * @param this The message
- * @param length The number of characters to remove
- * @param update_ptr Whether to update the buffer pointer
- */
-GCC_ONLY(__attribute__((nonnull)))
-static void unbuffer_beginning(struct message* restrict this, size_t length, int update_ptr)
-{
- memmove(this->buffer, this->buffer + length, (this->buffer_ptr - length) * sizeof(char));
- if (update_ptr)
- this->buffer_ptr -= length;
-}
-
-
-/**
- * Remove the header–payload delimiter from the buffer,
- * get the payload's size and allocate the payload
- *
- * @param this The message
- * @return The return value follows the rules of `message_read`
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int initialise_payload(struct message* restrict this)
-{
- /* Remove the \n (end of empty line) we found from the buffer. */
- unbuffer_beginning(this, 1, 1);
-
- /* Get the length of the payload. */
- if (get_payload_length(this) < 0)
- return -2; /* Malformated value, enters unrecoverable state. */
-
- /* Allocate the payload buffer. */
- if (this->payload_size > 0)
- if (!(this->payload = malloc(this->payload_size)))
- return -1;
-
- return 0;
-}
-
-
-/**
- * Create a header from the buffer and store it
- *
- * @param this The message
- * @param length The length of the header, including LF-termination
- * @return The return value follows the rules of `message_read`
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int store_header(struct message* restrict this, size_t length)
-{
- char* restrict header;
-
- /* Allocate the header. */
- if (!(header = malloc(length))) /* Last char is a LF, which is substituted with NUL. */
- return -1;
- /* Copy the header data into the allocated header, */
- memcpy(header, this->buffer, length * sizeof(char));
- /* and NUL-terminate it. */
- header[length - 1] = '\0';
-
- /* Remove the header data from the read buffer. */
- unbuffer_beginning(this, length, 1);
-
- /* Make sure the the header syntax is correct so that
- the program does not need to care about it. */
- if (validate_header(header, length))
- {
- free(header);
- return -2;
- }
-
- /* Store the header in the header list. */
- this->headers[this->header_count++] = header;
-
- return 0;
-}
-
-
-/**
- * Continue reading from the socket into the buffer
- *
- * @param this The message
- * @param fd The file descriptor of the socket
- * @return The return value follows the rules of `message_read`
- */
-GCC_ONLY(__attribute__((nonnull)))
-static int continue_read(struct message* restrict this, int fd)
-{
- size_t n;
- ssize_t got;
- int r;
-
- /* Figure out how much space we have left in the read buffer. */
- n = this->buffer_size - this->buffer_ptr;
-
- /* If we do not have too much left, */
- if (n < 128)
- {
- /* grow the buffer, */
- if ((r = extend_buffer(this)) < 0)
- return r;
-
- /* and recalculate how much space we have left. */
- n = this->buffer_size - this->buffer_ptr;
- }
-
- /* Then read from the socket. */
- errno = 0;
- got = recv(fd, this->buffer + this->buffer_ptr, n, 0);
- this->buffer_ptr += (size_t)(got < 0 ? 0 : got);
- if (errno)
- return -1;
- if (got == 0)
- {
- errno = ECONNRESET;
- return -1;
- }
-
- return 0;
-}
-
-
-/**
- * Read the next message from a file descriptor
- *
- * @param this Memory slot in which to store the new message
- * @param fd The file descriptor
- * @return 0: At least one message is available
- * -1: Exceptional connection:
- * EINTR: System call interrupted
- * EAGAIN: No message is available
- * EWOULDBLOCK: No message is available
- * ECONNRESET: Connection closed
- * Other: Failure
- * -2: Corrupt message (unrecoverable)
- */
-GCC_ONLY(__attribute__((nonnull)))
-int message_read(struct message* restrict this, int fd)
-{
- size_t header_commit_buffer = 0;
- int r;
-
- /* If we are at stage 2, we are done and it is time to start over.
- This is important because the function could have been interrupted. */
- if (this->stage == 2)
- {
- reset_message(this);
- this->stage = 0;
- }
-
- /* Read from file descriptor until we have a full message. */
- for (;;)
- {
- char* p;
- size_t length;
-
- /* Stage 0: headers. */
- /* Read all headers that we have stored into the read buffer. */
- while ((this->stage == 0) &&
- ((p = memchr(this->buffer, '\n', this->buffer_ptr * sizeof(char))) != NULL))
- if ((length = (size_t)(p - this->buffer)))
- {
- /* We have found a header. */
-
- /* On every eighth header found with this function call,
- we prepare the header list for eight more headers so
- that it does not need to be reallocated again and again. */
- if (header_commit_buffer == 0)
- if ((r = extend_headers(this, header_commit_buffer = 8)) < 0)
- return r;
-
- /* Create and store header. */
- if ((r = store_header(this, length + 1)) < 0)
- return r;
- header_commit_buffer -= 1;
- }
- else
- {
- /* We have found an empty line, i.e. the end of the headers. */
-
- /* Remove the header–payload delimiter from the buffer,
- get the payload's size and allocate the payload. */
- if ((r = initialise_payload(this)) < 0)
- return r;
-
- /* Mark end of stage, next stage is getting the payload. */
- this->stage = 1;
- }
-
-
- /* Stage 1: payload. */
- if ((this->stage == 1) && (this->payload_size > 0))
- {
- /* How much of the payload that has not yet been filled. */
- size_t need = this->payload_size - this->payload_ptr;
- /* How much we have of that what is needed. */
- size_t move = this->buffer_ptr < need ? this->buffer_ptr : need;
-
- /* Copy what we have, and remove it from the the read buffer. */
- memcpy(this->payload + this->payload_ptr, this->buffer, move * sizeof(char));
- unbuffer_beginning(this, move, 1);
-
- /* Keep track of how much we have read. */
- this->payload_ptr += move;
- }
- if ((this->stage == 1) && (this->payload_ptr == this->payload_size))
- {
- /* If we have filled the payload (or there was no payload),
- mark the end of this stage, i.e. that the message is
- complete, and return with success. */
- this->stage = 2;
- return 0;
- }
-
-
- /* If stage 1 was not completed. */
-
- /* Continue reading from the socket into the buffer. */
- if ((r = continue_read(this, fd)) < 0)
- return r;
- }
-}
-
diff --git a/src/types/message.h b/src/types/message.h
deleted file mode 100644
index 0f1ade2..0000000
--- a/src/types/message.h
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef TYPES_MESSAGE_H
-#define TYPES_MESSAGE_H
-
-
-#include <stddef.h>
-#include <limits.h>
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Message passed between a server and a client
- */
-struct message
-{
- /**
- * The headers in the message, each element in this list
- * as an unparsed header, it consists of both the header
- * name and its associated value, joined by ": ". A header
- * cannot be `NULL` (unless its memory allocation failed,)
- * but `headers` itself is `NULL` if there are no headers.
- * The "Length" header should be included in this list.
- */
- char** restrict headers;
-
- /**
- * The number of headers in the message
- */
- size_t header_count;
-
- /**
- * The payload of the message, `NULL` if none (of zero-length)
- */
- char* restrict payload;
-
- /**
- * The size of the payload
- */
- size_t payload_size;
-
- /**
- * How much of the payload that has been stored (internal data)
- */
- size_t payload_ptr;
-
- /**
- * Internal buffer for the reading function (internal data)
- */
- char* restrict buffer;
-
- /**
- * The size allocated to `buffer` (internal data)
- */
- size_t buffer_size;
-
- /**
- * The number of bytes used in `buffer` (internal data)
- */
- size_t buffer_ptr;
-
- /**
- * 0 while reading headers, 1 while reading payload, and 2 when done (internal data)
- */
- int stage;
-
-#if INT_MAX != LONG_MAX
- int padding__;
-#endif
-
-};
-
-
-
-/**
- * Initialise a message slot so that it can
- * be used by to read messages
- *
- * @param this Memory slot in which to store the new message
- * @return Non-zero on error, `errno` will be set accordingly
- */
-GCC_ONLY(__attribute__((nonnull)))
-int message_initialise(struct message* restrict this);
-
-/**
- * Release all resources in a message, should
- * be done even if initialisation fails
- *
- * @param this The message
- */
-GCC_ONLY(__attribute__((nonnull)))
-void message_destroy(struct message* restrict this);
-
-/**
- * Marshal a message for state serialisation
- *
- * @param this The message
- * @param buf Output buffer for the marshalled data,
- * `NULL` just measure how large the buffers
- * needs to be
- * @return The number of marshalled byte
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-size_t message_marshal(const struct message* restrict this, void* restrict buf);
-
-/**
- * Unmarshal a message for state deserialisation
- *
- * @param this Memory slot in which to store the new message
- * @param buf In buffer with the marshalled data
- * @return The number of unmarshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-size_t message_unmarshal(struct message* restrict this, const void* restrict buf);
-
-/**
- * Read the next message from a file descriptor
- *
- * @param this Memory slot in which to store the new message
- * @param fd The file descriptor
- * @return 0: At least one message is available
- * -1: Exceptional connection:
- * EINTR: System call interrupted
- * EAGAIN: No message is available
- * EWOULDBLOCK: No message is available
- * ECONNRESET: Connection closed
- * Other: Failure
- * -2: Corrupt message (unrecoverable)
- */
-GCC_ONLY(__attribute__((nonnull)))
-int message_read(struct message* restrict this, int fd);
-
-
-#endif
-
diff --git a/src/types/output.c b/src/types/output.c
deleted file mode 100644
index b94d090..0000000
--- a/src/types/output.c
+++ /dev/null
@@ -1,335 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "output.h"
-#include "../util.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * Free all resources allocated to an output.
- * The allocation of `output` itself is not freed,
- * nor is its the libgamma destroyed.
- *
- * @param this The output
- */
-void output_destroy(struct output* restrict this)
-{
- size_t i;
-
- if (this->supported != LIBGAMMA_NO)
- switch (this->depth)
- {
- case 8:
- libgamma_gamma_ramps8_destroy(&(this->saved_ramps.u8));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_ramps8_destroy(&(this->table_sums[i].u8));
- break;
- case 16:
- libgamma_gamma_ramps16_destroy(&(this->saved_ramps.u16));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_ramps16_destroy(&(this->table_sums[i].u16));
- break;
- case 32:
- libgamma_gamma_ramps32_destroy(&(this->saved_ramps.u32));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_ramps32_destroy(&(this->table_sums[i].u32));
- break;
- case 64:
- libgamma_gamma_ramps64_destroy(&(this->saved_ramps.u64));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_ramps64_destroy(&(this->table_sums[i].u64));
- break;
- case -1:
- libgamma_gamma_rampsf_destroy(&(this->saved_ramps.f));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_rampsf_destroy(&(this->table_sums[i].f));
- break;
- case -2:
- libgamma_gamma_rampsd_destroy(&(this->saved_ramps.d));
- for (i = 0; i < this->table_size; i++)
- libgamma_gamma_rampsd_destroy(&(this->table_sums[i].d));
- break;
- default:
- break; /* impossible */
- }
-
- for (i = 0; i < this->table_size; i++)
- filter_destroy(this->table_filters + i);
-
- free(this->table_filters);
- free(this->table_sums);
- free(this->name);
-}
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal an output
- *
- * @param this The output
- * @param buf Output buffer for the marshalled output,
- * `NULL` just measure how large the buffers
- * needs to be
- * @return The number of marshalled byte
- */
-size_t output_marshal(const struct output* restrict this, void* restrict buf)
-{
- size_t off = 0, i, n;
- char* bs = buf;
-
- if (bs != NULL)
- *(signed*)(bs + off) = this->depth;
- off += sizeof(signed);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->red_size;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->green_size;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->blue_size;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->ramps_size;
- off += sizeof(size_t);
-
- if (bs != NULL)
- *(enum libgamma_decision*)(bs + off) = this->supported;
- off += sizeof(enum libgamma_decision);
-
- if (bs != NULL)
- *(enum colourspace*)(bs + off) = this->colourspace;
- off += sizeof(enum colourspace);
-
- if (bs != NULL)
- *(int*)(bs + off) = this->name_is_edid;
- off += sizeof(int);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->red_x;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->red_y;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->green_x;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->green_y;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->blue_x;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->blue_y;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->white_x;
- off += sizeof(unsigned);
-
- if (bs != NULL)
- *(unsigned*)(bs + off) = this->white_y;
- off += sizeof(unsigned);
-
- n = strlen(this->name) + 1;
- if (bs != NULL)
- memcpy(bs + off, this->name, n);
- off += n;
-
- off += gamma_ramps_marshal(&(this->saved_ramps), bs ? bs + off : NULL, this->ramps_size);
-
- if (bs != NULL)
- *(size_t*)(bs + off) = this->table_size;
- off += sizeof(size_t);
-
- for (i = 0; i < this->table_size; i++)
- {
- off += filter_marshal(this->table_filters + i, bs ? bs + off : NULL, this->ramps_size);
- off += gamma_ramps_marshal(this->table_sums + i, bs ? bs + off : NULL, this->ramps_size);
- }
-
- return off;
-}
-
-
-/**
- * Unmarshal an output
- *
- * @param this Output for the output
- * @param buf Buffer with the marshalled output
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t output_unmarshal(struct output* restrict this, const void* restrict buf)
-{
- size_t off = 0, i, n;
- const char* bs = buf;
-
- this->crtc = NULL;
- this->name = NULL;
-
- this->depth = *(const signed*)(bs + off);
- off += sizeof(signed);
-
- this->red_size = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->green_size = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->blue_size = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->ramps_size = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- this->supported = *(const enum libgamma_decision*)(bs + off);
- off += sizeof(enum libgamma_decision);
-
- this->colourspace = *(const enum colourspace*)(bs + off);
- off += sizeof(enum colourspace);
-
- this->name_is_edid = *(const int*)(bs + off);
- off += sizeof(int);
-
- this->red_x = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->red_y = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->green_x = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->green_y = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->blue_x = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->blue_y = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->white_x = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- this->white_y = *(const unsigned*)(bs + off);
- off += sizeof(unsigned);
-
- n = strlen(bs + off) + 1;
- this->name = memdup(bs + off, n);
- if (this->name == NULL)
- return 0;
- off += n;
-
- COPY_RAMP_SIZES(&(this->saved_ramps.u8), this);
- off += n = gamma_ramps_unmarshal(&(this->saved_ramps), bs + off, this->ramps_size);
- if (n == 0)
- return 0;
-
- this->table_size = this->table_alloc = *(const size_t*)(bs + off);
- off += sizeof(size_t);
- if (this->table_size > 0)
- {
- this->table_filters = calloc(this->table_size, sizeof(*(this->table_filters)));
- if (this->table_filters == NULL)
- return 0;
- this->table_sums = calloc(this->table_size, sizeof(*(this->table_sums)));
- if (this->table_sums == NULL)
- return 0;
- }
-
- for (i = 0; i < this->table_size; i++)
- {
- off += n = filter_unmarshal(this->table_filters + i, bs + off, this->ramps_size);
- if (n == 0)
- return 0;
- COPY_RAMP_SIZES(&(this->table_sums[i].u8), this);
- off += n = gamma_ramps_unmarshal(this->table_sums + i, bs + off, this->ramps_size);
- if (n == 0)
- return 0;
- }
-
- return off;
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
-
-
-
-/**
- * Compare to outputs by the names of their respective CRTC:s
- *
- * @param a Return -1 if this one is lower
- * @param b Return +1 if this one is higher
- * @return See description of `a` and `b`,
- * 0 if returned if they are the same
- */
-int output_cmp_by_name(const void* restrict a, const void* restrict b)
-{
- const char* an = ((const struct output*)a)->name;
- const char* bn = ((const struct output*)b)->name;
- return strcmp(an, bn);
-}
-
-
-/**
- * Find an output by its name
- *
- * @param key The name of the output
- * @param base The array of outputs
- * @param n The number of elements in `base`
- * @return Output find in `base`, `NULL` if not found
- */
-struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n)
-{
- struct output k;
-
-#if defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-qual"
-#endif
- k.name = (char*)key;
-#if defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
-
- return bsearch(&k, base, n, sizeof(*base), output_cmp_by_name);
-}
-
diff --git a/src/types/output.h b/src/types/output.h
deleted file mode 100644
index 750ec41..0000000
--- a/src/types/output.h
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef TYPES_OUTPUT_H
-#define TYPES_OUTPUT_H
-
-
-#include <stddef.h>
-
-#include <libgamma.h>
-
-#include "ramps.h"
-#include "filter.h"
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Copy the ramp sizes
- *
- * This macro supports both `struct output`
- * and `struct gamma_ramps`
- *
- * @param dest The destination
- * @param src The source
- */
-#define COPY_RAMP_SIZES(dest, src) \
- ((dest)->red_size = (src)->red_size, \
- (dest)->green_size = (src)->green_size, \
- (dest)->blue_size = (src)->blue_size)
-
-
-
-/**
- * Colour spaces
- */
-enum colourspace
-{
-
- /**
- * Unknown
- */
- COLOURSPACE_UNKNOWN = 0,
-
- /**
- * sRGB with explicit gamut
- */
- COLOURSPACE_SRGB = 1,
-
- /**
- * sRGB without explicit gamut
- */
- COLOURSPACE_SRGB_SANS_GAMUT = 2,
-
- /**
- * RGB (but not sRGB) with known gamut
- */
- COLOURSPACE_RGB = 3,
-
- /**
- * RGB (but not sRGB) without known gamut
- */
- COLOURSPACE_RGB_SANS_GAMUT = 4,
-
- /**
- * Non-RGB multicolour
- */
- COLOURSPACE_NON_RGB = 5,
-
- /**
- * Greyscale or monochrome
- */
- COLOURSPACE_GREY = 6
-};
-
-
-
-/**
- * Information about an output
- */
-struct output
-{
- /**
- * -2: double
- * -1: float
- * 8: uint8_t
- * 16: uint16_t
- * 32: uint32_t
- * 64: uint64_t
- */
- signed depth;
-
- /**
- * Whether gamma ramps are supported
- */
- enum libgamma_decision supported;
-
- /**
- * Whether the name is the EDID
- */
- int name_is_edid;
-
- /**
- * The monitor's colour space
- */
- enum colourspace colourspace;
-
- /**
- * The x-value (CIE xyY) of the monitor's
- * red colour, multiplied by 1024
- */
- unsigned red_x;
-
- /**
- * The y-value (CIE xyY) of the monitor's
- * red colour, multiplied by 1024
- */
- unsigned red_y;
-
- /**
- * The x-value (CIE xyY) of the monitor's
- * green colour, multiplied by 1024
- */
- unsigned green_x;
-
- /**
- * The y-value (CIE xyY) of the monitor's
- * green colour, multiplied by 1024
- */
- unsigned green_y;
-
- /**
- * The x-value (CIE xyY) of the monitor's
- * blue colour, multiplied by 1024
- */
- unsigned blue_x;
-
- /**
- * The y-value (CIE xyY) of the monitor's
- * blue colour, multiplied by 1024
- */
- unsigned blue_y;
-
- /**
- * The x-value (CIE xyY) of the monitor's
- * default white point, multiplied by 1024
- */
- unsigned white_x;
-
- /**
- * The y-value (CIE xyY) of the monitor's
- * default white point, multiplied by 1024
- */
- unsigned white_y;
-
- /**
- * The number of stops in the red gamma ramp
- */
- size_t red_size;
-
- /**
- * The number of stops in the green gamma ramp
- */
- size_t green_size;
-
- /**
- * The number of stops in the blue gamma ramp
- */
- size_t blue_size;
-
- /**
- * `.red_size + .green_size + .blue_size`
- * multiplied by the byte-size of each stop
- */
- size_t ramps_size;
-
- /**
- * The name of the output, will be its EDID
- * if available, otherwise it will be the
- * index of the partition, followed by a dot
- * and the index of the CRTC within the
- * partition, or if a name for the connector
- * is available: the index of the partition
- * followed by a dot and the name of the
- * connector
- */
- char* restrict name;
-
- /**
- * The libgamma state for the output
- */
- libgamma_crtc_state_t* restrict crtc;
-
- /**
- * Saved gamma ramps
- */
- union gamma_ramps saved_ramps;
-
- /**
- * The table of all applied filters
- */
- struct filter* restrict table_filters;
-
- /**
- * `.table_sums[i]` is the resulting
- * adjustment made when all filter
- * from `.table_filters[0]` up to and
- * including `.table_filters[i]` has
- * been applied
- */
- union gamma_ramps* restrict table_sums;
-
- /**
- * The number of elements allocated
- * for `.table_filters` and for `.table_sums`
- */
- size_t table_alloc;
-
- /**
- * The number of elements stored in
- * `.table_filters` and in `.table_sums`
- */
- size_t table_size;
-
-};
-
-
-
-/**
- * Free all resources allocated to an output.
- * The allocation of `output` itself is not freed,
- * nor is its the libgamma destroyed.
- *
- * @param this The output
- */
-GCC_ONLY(__attribute__((nonnull)))
-void output_destroy(struct output* restrict this);
-
-/**
- * Marshal an output
- *
- * @param this The output
- * @param buf Output buffer for the marshalled output,
- * `NULL` just measure how large the buffers
- * needs to be
- * @return The number of marshalled byte
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-size_t output_marshal(const struct output* restrict this, void* restrict buf);
-
-/**
- * Unmarshal an output
- *
- * @param this Output for the output
- * @param buf Buffer with the marshalled output
- * @return The number of unmarshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-size_t output_unmarshal(struct output* restrict this, const void* restrict buf);
-
-/**
- * Compare to outputs by the names of their respective CRTC:s
- *
- * @param a Return -1 if this one is lower
- * @param b Return +1 if this one is higher
- * @return See description of `a` and `b`,
- * 0 if returned if they are the same
- */
-GCC_ONLY(__attribute__((pure, nonnull)))
-int output_cmp_by_name(const void* restrict a, const void* restrict b);
-
-/**
- * Find an output by its name
- *
- * @param key The name of the output
- * @param base The array of outputs
- * @param n The number of elements in `base`
- * @return Output find in `base`, `NULL` if not found
- */
-GCC_ONLY(__attribute__((pure, nonnull)))
-struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n);
-
-
-#endif
-
diff --git a/src/types/ramps.c b/src/types/ramps.c
deleted file mode 100644
index 30bed3e..0000000
--- a/src/types/ramps.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "ramps.h"
-
-#include <libclut.h>
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * The name of the process
- */
-extern char* restrict argv0;
-
-
-
-/**
- * Marshal a ramp trio
- *
- * @param this The ramps
- * @param buf Output buffer for the marshalled ramps,
- * `NULL` just measure how large the buffers
- * needs to be
- * @param ramps_size The byte-size of ramps
- * @return The number of marshalled byte
- */
-size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size)
-{
- if (buf != NULL)
- memcpy(buf, this->u8.red, ramps_size);
- return ramps_size;
-}
-
-
-/**
- * Unmarshal a ramp trio
- *
- * @param this Output for the ramps, `.red_size`, `.green_size`,
- * and `.blue_size` must already be set
- * @param buf Buffer with the marshalled ramps
- * @param ramps_size The byte-size of ramps
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size)
-{
- size_t depth = ramps_size / (this->u8.red_size + this->u8.green_size + this->u8.blue_size);
- int r = 0;
- switch (depth)
- {
- case 1:
- r = libgamma_gamma_ramps8_initialise(&(this->u8));
- break;
- case 2:
- r = libgamma_gamma_ramps16_initialise(&(this->u16));
- break;
- case 4:
- r = libgamma_gamma_ramps32_initialise(&(this->u32));
- break;
- case 8:
- r = libgamma_gamma_ramps64_initialise(&(this->u64));
- break;
- default:
- if (depth == sizeof(float))
- r = libgamma_gamma_rampsf_initialise(&(this->f));
- else if (depth == sizeof(double))
- r = libgamma_gamma_rampsd_initialise(&(this->d));
- else
- abort();
- break;
- }
- if (r)
- {
- libgamma_perror(argv0, r);
- errno = 0;
- return 0;
- }
- memcpy(this->u8.red, buf, ramps_size);
- return ramps_size;
-}
-
diff --git a/src/types/ramps.h b/src/types/ramps.h
deleted file mode 100644
index 001d504..0000000
--- a/src/types/ramps.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef TYPES_RAMPS_H
-#define TYPES_RAMPS_H
-
-
-#include <libgamma.h>
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Gamma ramps union for all
- * lbigamma gamma ramps types
- */
-union gamma_ramps
-{
- /**
- * Ramps with 8-bit value
- */
- libgamma_gamma_ramps8_t u8;
-
- /**
- * Ramps with 16-bit value
- */
- libgamma_gamma_ramps16_t u16;
-
- /**
- * Ramps with 32-bit value
- */
- libgamma_gamma_ramps32_t u32;
-
- /**
- * Ramps with 64-bit value
- */
- libgamma_gamma_ramps64_t u64;
-
- /**
- * Ramps with `float` value
- */
- libgamma_gamma_rampsf_t f;
-
- /**
- * Ramps with `double` value
- */
- libgamma_gamma_rampsd_t d;
-
-};
-
-
-
-/**
- * Marshal a ramp trio
- *
- * @param this The ramps
- * @param buf Output buffer for the marshalled ramps,
- * `NULL` just measure how large the buffers
- * needs to be
- * @param ramps_size The byte-size of ramps
- * @return The number of marshalled byte
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size);
-
-/**
- * Unmarshal a ramp trio
- *
- * @param this Output for the ramps
- * @param buf Buffer with the marshalled ramps
- * @param ramps_size The byte-size of ramps
- * @return The number of unmarshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size);
-
-
-#endif
-
diff --git a/src/types/ring.c b/src/types/ring.c
deleted file mode 100644
index 13cf8c9..0000000
--- a/src/types/ring.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "ring.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-
-
-/**
- * Initialise a ring buffer
- *
- * @param this The ring buffer
- */
-void ring_initialise(struct ring* restrict this)
-{
- this->start = 0;
- this->end = 0;
- this->size = 0;
- this->buffer = NULL;
-}
-
-
-/**
- * Release resource allocated to a ring buffer
- *
- * @param this The ring buffer
- */
-void ring_destroy(struct ring* restrict this)
-{
- free(this->buffer);
-}
-
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wcast-align"
-#endif
-
-
-/**
- * Marshal a ring buffer
- *
- * @param this The ring buffer
- * @param buf Output buffer for the marshalled data,
- * `NULL` to only measure how large buffer
- * is needed
- * @return The number of marshalled bytes
- */
-size_t ring_marshal(const struct ring* restrict this, void* restrict buf)
-{
- size_t off = 0, n = this->end - this->start;
- char* bs = buf;
-
- if (bs != NULL)
- *(size_t*)(bs + off) = n;
- off += sizeof(size_t);
-
- if (bs != NULL)
- memcpy(bs + off, this->buffer + this->start, n);
- off += n;
-
- return off;
-}
-
-
-/**
- * Unmarshal a ring buffer
- *
- * @param this Output parameter for the ring buffer
- * @param buf Buffer with the marshalled data
- * @return The number of unmarshalled bytes, 0 on error
- */
-size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf)
-{
- size_t off = 0;
- const char* bs = buf;
-
- ring_initialise(this);
-
- this->size = this->end = *(const size_t*)(bs + off);
- off += sizeof(size_t);
-
- if (this->end > 0)
- {
- if (!(this->buffer = malloc(this->end)))
- return 0;
-
- memcpy(this->buffer, bs + off, this->end);
- off += this->end;
- }
-
- return off;
-}
-
-
-#if defined(__clang__)
-# pragma GCC diagnostic pop
-#endif
-
-
-
-/**
- * Append data to a ring buffer
- *
- * @param this The ring buffer
- * @param data The new data
- * @param n The number of bytes in `data`
- * @return Zero on success, -1 on error
- */
-int ring_push(struct ring* restrict this, void* restrict data, size_t n)
-{
- size_t used = 0;
-
- if (this->start == this->end)
- {
- if (this->buffer != NULL)
- used = this->size;
- }
- else if (this->start > this->end)
- used = this->size - this->start + this->end;
- else
- used = this->start - this->end;
-
- if (used + n > this->size)
- {
- char* restrict new = malloc(used + n);
- if (new == NULL)
- return -1;
- if (this->buffer)
- {
- if (this->start < this->end)
- memcpy(new, this->buffer + this->start, this->end - this->start);
- else
- {
- memcpy(new, this->buffer + this->start, this->size - this->start);
- memcpy(new + this->size - this->start, this->buffer, this->end);
- }
- }
- memcpy(new + used, data, n);
- this->buffer = new;
- this->start = 0;
- this->end = used + n;
- this->size = used + n;
- }
- else if ((this->start >= this->end) || (this->end + n <= this->size))
- {
- memcpy(this->buffer + this->end, data, n);
- this->end += n;
- }
- else
- {
- memcpy(this->buffer + this->end, data, this->size - this->end);
- data = (char*)data + (this->size - this->end);
- n -= this->size - this->end;
- memcpy(this->buffer, data, n);
- this->end = n;
- }
-
- return 0;
-}
-
-
-/**
- * Get queued data from a ring buffer
- *
- * It can take up to two calls (with `ring_pop` between)
- * to get all queued data
- *
- * @param this The ring buffer
- * @param n Output parameter for the length
- * of the returned segment
- * @return The beginning of the queued data,
- * `NULL` if there is nothing more
- */
-void* ring_peek(struct ring* restrict this, size_t* restrict n)
-{
- if (this->buffer == NULL)
- return *n = 0, NULL;
-
- if (this->start < this->end)
- *n = this->end - this->start;
- else
- *n = this->size - this->start;
- return this->buffer + this->start;
-}
-
-
-/**
- * Dequeue data from a ring bubber
- *
- * @param this The ring buffer
- * @param n The number of bytes to dequeue
- */
-void ring_pop(struct ring* restrict this, size_t n)
-{
- this->start += n;
- this->start %= this->size;
- if (this->start == this->end)
- {
- free(this->buffer);
- this->start = 0;
- this->end = 0;
- this->size = 0;
- this->buffer = NULL;
- }
-}
-
diff --git a/src/types/ring.h b/src/types/ring.h
deleted file mode 100644
index 0474f39..0000000
--- a/src/types/ring.h
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef TYPES_RING_H
-#define TYPES_RING_H
-
-
-#include <stddef.h>
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Ring buffer
- */
-struct ring
-{
- /**
- * Buffer for the data
- */
- char* restrict buffer;
-
- /**
- * The first set byte in `.buffer`
- */
- size_t start;
-
- /**
- * The last set byte in `.buffer`, plus 1
- */
- size_t end;
-
- /**
- * The size of `.buffer`
- */
- size_t size;
-};
-
-
-
-/**
- * Initialise a ring buffer
- *
- * @param this The ring buffer
- */
-GCC_ONLY(__attribute__((nonnull)))
-void ring_initialise(struct ring* restrict this);
-
-/**
- * Release resource allocated to a ring buffer
- *
- * @param this The ring buffer
- */
-GCC_ONLY(__attribute__((nonnull)))
-void ring_destroy(struct ring* restrict this);
-
-/**
- * Marshal a ring buffer
- *
- * @param this The ring buffer
- * @param buf Output buffer for the marshalled data,
- * `NULL` to only measure how large buffer
- * is needed
- * @return The number of marshalled bytes
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-size_t ring_marshal(const struct ring* restrict this, void* restrict buf);
-
-/**
- * Unmarshal a ring buffer
- *
- * @param this Output parameter for the ring buffer
- * @param buf Buffer with the marshalled data
- * @return The number of unmarshalled bytes, 0 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf);
-
-/**
- * Append data to a ring buffer
- *
- * @param this The ring buffer
- * @param data The new data
- * @param n The number of bytes in `data`
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull(1))))
-int ring_push(struct ring* restrict this, void* restrict data, size_t n);
-
-/**
- * Get queued data from a ring buffer
- *
- * It can take up to two calls (with `ring_pop` between)
- * to get all queued data
- *
- * @param this The ring buffer
- * @param n Output parameter for the length
- * of the returned segment
- * @return The beginning of the queued data,
- * `NULL` if there is nothing more
- */
-GCC_ONLY(__attribute__((nonnull)))
-void* ring_peek(struct ring* restrict this, size_t* restrict n);
-
-/**
- * Dequeue data from a ring bubber
- *
- * @param this The ring buffer
- * @param n The number of bytes to dequeue
- */
-GCC_ONLY(__attribute__((nonnull)))
-void ring_pop(struct ring* restrict this, size_t n);
-
-/**
- * Check whether there is more data waiting
- * in a ring buffer
- *
- * @param this The ring buffer
- * @return 1 if there is more data, 0 otherwise
- */
-GCC_ONLY(__attribute__((nonnull)))
-static inline int ring_have_more(struct ring* restrict this)
-{
- return this->buffer != NULL;
-}
-
-
-#endif
-
diff --git a/src/util.c b/src/util.c
deleted file mode 100644
index 2fdbeae..0000000
--- a/src/util.c
+++ /dev/null
@@ -1,316 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#include "util.h"
-
-#include <libclut.h>
-
-#include <sys/stat.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-
-
-/**
- * Duplicate a memory segment
- *
- * @param src The memory segment, must not be `NULL`
- * @param n The size of the memory segment, must not be zero
- * @return The duplicate of the memory segment,
- * `NULL` on error
- */
-void* memdup(const void* restrict src, size_t n)
-{
- void* dest = malloc(n);
- if (dest == NULL)
- return NULL;
- memcpy(dest, src, n);
- return dest;
-}
-
-
-/**
- * Read an entire file
- *
- * @param fd The file descriptor
- * @param n Output for the size of the file
- * @return The read content, plus a NUL byte at
- * the end (not counted in `*n`)
- */
-void* nread(int fd, size_t* restrict n)
-{
- size_t size = 32;
- ssize_t got;
- struct stat st;
- char* buf = NULL;
- char* new;
- int saved_errno;
-
- *n = 0;
-
- if (!fstat(fd, &st))
- size = st.st_size <= 0 ? 32 : (size_t)(st.st_size);
-
- buf = malloc(size + 1);
- if (buf == NULL)
- return NULL;
-
- for (;;)
- {
- if (*n == size)
- {
- new = realloc(buf, (size <<= 1) + 1);
- if (new == NULL)
- goto fail;
- buf = new;
- }
-
- got = read(fd, buf + *n, size - *n);
- if (got < 0)
- {
- if (errno == EINTR)
- continue;
- goto fail;
- }
- if (got == 0)
- break;
- *n += (size_t)got;
- }
-
- buf[*n] = '\0';
- return buf;
- fail:
- saved_errno = errno;
- free(buf);
- errno = saved_errno;
- return NULL;
-}
-
-
-/**
- * Write an entire buffer to a file
- *
- * Not cancelled by `EINTR`
- *
- * @param fd The file descriptor
- * @param buf The buffer which shall be written to the fail
- * @param n The size of the buffer
- * @return The number of written bytes, less than `n`
- * on error, cannot exceed `n`
- */
-size_t nwrite(int fd, const void* restrict buf, size_t n)
-{
- const char* restrict bs = buf;
- ssize_t wrote;
- size_t ptr = 0;
-
- while (ptr < n)
- {
- wrote = write(fd, bs + ptr, n - ptr);
- if (wrote <= 0)
- {
- if ((wrote < 0) && (errno == EINTR))
- continue;
- return ptr;
- }
- ptr += (size_t)wrote;
- }
-
- return ptr;
-}
-
-
-/**
- * Duplicate a file descriptor an make sure
- * the new file descriptor's index as a
- * specified minimum value
- *
- * @param fd The file descriptor
- * @param atleast The least acceptable new file descriptor
- * @return The new file descriptor, -1 on error
- */
-int dup2atleast(int fd, int atleast)
-{
- int* stack = malloc((size_t)(atleast + 1) * sizeof(int));
- size_t stack_ptr = 0;
- int new = -1, saved_errno;
-
- if (stack == NULL)
- goto fail;
-
- for (;;)
- {
- new = dup(fd);
- if (new < 0)
- goto fail;
- if (new >= atleast)
- break;
- }
-
- fail:
- saved_errno = errno;
- while (stack_ptr--)
- close(stack[stack_ptr]);
- free(stack);
- errno = saved_errno;
- return new;
-}
-
-
-/**
- * Perform a timed suspention of the process.
- * The process resumes when the timer expires,
- * or when it is interrupted.
- *
- * @param ms The number of milliseconds to sleep,
- * must be less than 1000
- */
-void msleep(unsigned ms)
-{
- struct timespec ts;
- ts.tv_sec = 0;
- ts.tv_nsec = (long)ms * 1000000L;
- if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL) == ENOTSUP)
- nanosleep(&ts, NULL);
-}
-
-
-/**
- * Check whether a NUL-terminated string is encoded in UTF-8
- *
- * @param string The string
- * @return Zero if good, -1 on encoding error
- */
-int verify_utf8(const char* restrict string)
-{
- static long BYTES_TO_MIN_BITS[] = {0, 0, 8, 12, 17, 22, 37};
- static long BYTES_TO_MAX_BITS[] = {0, 7, 11, 16, 21, 26, 31};
- long bytes = 0, read_bytes = 0, bits = 0, c, character = 0;
-
- /* min bits max bits
- 0....... 0 7
- 110..... 10...... 8 11
- 1110.... 10...... 10...... 12 16
- 11110... 10...... 10...... 10...... 17 21
- 111110.. 10...... 10...... 10...... 10...... 22 26
- 1111110. 10...... 10...... 10...... 10...... 10...... 27 31
- */
-
- while ((c = (long)(*string++)))
- if (read_bytes == 0)
- {
- /* First byte of the character. */
-
- if ((c & 0x80) == 0x00)
- /* Single-byte character. */
- continue;
-
- if ((c & 0xC0) == 0x80)
- /* Single-byte character marked as multibyte, or
- a non-first byte in a multibyte character. */
- return -1;
-
- /* Multibyte character. */
- while ((c & 0x80))
- bytes++, c <<= 1;
- read_bytes = 1;
- character = c & 0x7F;
- if (bytes > 6)
- /* 31-bit characters can be encoded with 6-bytes,
- and UTF-8 does not cover higher code points. */
- return -1;
- }
- else
- {
- /* Not first byte of the character. */
-
- if ((c & 0xC0) != 0x80)
- /* Beginning of new character before a
- multibyte character has ended. */
- return -1;
-
- character = (character << 6) | (c & 0x7F);
-
- if (++read_bytes < bytes)
- /* Not at last byte yet. */
- continue;
-
- /* Check that the character is not unnecessarily long. */
- while (character)
- character >>= 1, bits++;
- if ((bits < BYTES_TO_MIN_BITS[bytes]) || (BYTES_TO_MAX_BITS[bytes] < bits))
- return -1;
-
- read_bytes = bytes = bits = 0;
- }
-
- /* Make sure we did not stop at the middle of a multibyte character. */
- return read_bytes == 0 ? 0 : -1;
-}
-
-
-/**
- * Make identity mapping ramps
- *
- * @param ramps Output parameter for the ramps
- * @param output The output for which the ramps shall be configured
- * @return Zero on success, -1 on error
- */
-int make_plain_ramps(union gamma_ramps* restrict ramps, struct output* restrict output)
-{
- COPY_RAMP_SIZES(&(ramps->u8), output);
- switch (output->depth)
- {
- case 8:
- if (libgamma_gamma_ramps8_initialise(&(ramps->u8)))
- return -1;
- libclut_start_over(&(ramps->u8), UINT8_MAX, uint8_t, 1, 1, 1);
- break;
- case 16:
- if (libgamma_gamma_ramps16_initialise(&(ramps->u16)))
- return -1;
- libclut_start_over(&(ramps->u16), UINT16_MAX, uint16_t, 1, 1, 1);
- break;
- case 32:
- if (libgamma_gamma_ramps32_initialise(&(ramps->u32)))
- return -1;
- libclut_start_over(&(ramps->u32), UINT32_MAX, uint32_t, 1, 1, 1);
- break;
- case 64:
- if (libgamma_gamma_ramps64_initialise(&(ramps->u64)))
- return -1;
- libclut_start_over(&(ramps->u64), UINT64_MAX, uint64_t, 1, 1, 1);
- break;
- case -1:
- if (libgamma_gamma_rampsf_initialise(&(ramps->f)))
- return -1;
- libclut_start_over(&(ramps->f), 1.0f, float, 1, 1, 1);
- break;
- case -2:
- if (libgamma_gamma_rampsd_initialise(&(ramps->d)))
- return -1;
- libclut_start_over(&(ramps->d), (double)1, double, 1, 1, 1);
- break;
- default:
- abort();
- }
- return 0;
-}
-
diff --git a/src/util.h b/src/util.h
deleted file mode 100644
index eb98285..0000000
--- a/src/util.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifndef UTIL_H
-#define UTIL_H
-
-
-#include "types/output.h"
-
-
-
-#ifndef GCC_ONLY
-# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
-# else
-# define GCC_ONLY(...) /* nothing */
-# endif
-#endif
-
-
-
-/**
- * Duplicate a memory segment
- *
- * @param src The memory segment, must not be `NULL`
- * @param n The size of the memory segment, must not be zero
- * @return The duplicate of the memory segment,
- * `NULL` on error
- */
-GCC_ONLY(__attribute__((malloc, nonnull)))
-void* memdup(const void* restrict src, size_t n);
-
-/**
- * Read an entire file
- *
- * Not cancelled by `EINTR`
- *
- * @param fd The file descriptor
- * @param n Output for the size of the file
- * @return The read content, plus a NUL byte at
- * the end (not counted in `*n`)
- */
-GCC_ONLY(__attribute__((malloc)))
-void* nread(int fd, size_t* restrict n);
-
-/**
- * Write an entire buffer to a file
- *
- * Not cancelled by `EINTR`
- *
- * @param fd The file descriptor
- * @param buf The buffer which shall be written to the fail
- * @param n The size of the buffer
- * @return The number of written bytes, less than `n`
- * on error, cannot exceed `n`
- */
-size_t nwrite(int fd, const void* restrict buf, size_t n);
-
-/**
- * Duplicate a file descriptor an make sure
- * the new file descriptor's index as a
- * specified minimum value
- *
- * @param fd The file descriptor
- * @param atleast The least acceptable new file descriptor
- * @return The new file descriptor, -1 on error
- */
-int dup2atleast(int fd, int atleast);
-
-/**
- * Perform a timed suspention of the process.
- * The process resumes when the timer expires,
- * or when it is interrupted.
- *
- * @param ms The number of milliseconds to sleep,
- * must be less than 1000
- */
-void msleep(unsigned ms);
-
-/**
- * Check whether a NUL-terminated string is encoded in UTF-8
- *
- * @param string The string
- * @return Zero if good, -1 on encoding error
- */
-GCC_ONLY(__attribute__((pure, nonnull)))
-int verify_utf8(const char* restrict string);
-
-/**
- * Make identity mapping ramps
- *
- * @param ramps Output parameter for the ramps
- * @param output The output for which the ramps shall be configured
- * @return Zero on success, -1 on error
- */
-GCC_ONLY(__attribute__((nonnull)))
-int make_plain_ramps(union gamma_ramps* restrict ramps, struct output* restrict output);
-
-
-#endif
-
diff --git a/state.c b/state.c
new file mode 100644
index 0000000..5aabf0e
--- /dev/null
+++ b/state.c
@@ -0,0 +1,612 @@
+/* See LICENSE file for copyright and license details. */
+#include "state.h"
+#include "util.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * The name of the process
+ */
+char *restrict argv0; /* do not marshal */
+
+/**
+ * The real pathname of the process's binary,
+ * `NULL` if `argv0` is satisfactory
+ */
+char *restrict argv0_real = NULL;
+
+/**
+ * Array of all outputs
+ */
+struct output *restrict outputs = NULL;
+
+/**
+ * The nubmer of elements in `outputs`
+ */
+size_t outputs_n = 0;
+
+/**
+ * The server socket's file descriptor
+ */
+int socketfd = -1;
+
+/**
+ * Has the process receive a signal
+ * telling it to re-execute?
+ */
+volatile sig_atomic_t reexec = 0; /* do not marshal */
+
+/**
+ * Has the process receive a signal
+ * telling it to terminate?
+ */
+volatile sig_atomic_t terminate = 0; /* do not marshal */
+
+/**
+ * Has the process receive a to
+ * disconnect from or reconnect to
+ * the site? 1 if disconnect, 2 if
+ * reconnect, 0 otherwise.
+ */
+volatile sig_atomic_t connection = 0;
+
+/**
+ * List of all client's file descriptors
+ *
+ * Unused slots, with index less than `connections_used`,
+ * should have the value -1 (negative)
+ */
+int *restrict connections = NULL;
+
+/**
+ * The number of elements allocated for `connections`
+ */
+size_t connections_alloc = 0;
+
+/**
+ * The index of the first unused slot in `connections`
+ */
+size_t connections_ptr = 0;
+
+/**
+ * The index of the last used slot in `connections`, plus 1
+ */
+size_t connections_used = 0;
+
+/**
+ * The clients' connections' inbound-message buffers
+ */
+struct message *restrict inbound = NULL;
+
+/**
+ * The clients' connections' outbound-message buffers
+ */
+struct ring *restrict outbound = NULL;
+
+/**
+ * Is the server connect to the display?
+ *
+ * Set to true before the initial connection
+ */
+int connected = 1;
+
+/**
+ * The adjustment method, -1 for automatic
+ */
+int method = -1;
+
+/**
+ * The site's name, may be `NULL`
+ */
+char *restrict sitename = NULL;
+
+/**
+ * The libgamma site state
+ */
+libgamma_site_state_t site; /* do not marshal */
+
+/**
+ * The libgamma partition states
+ */
+libgamma_partition_state_t *restrict partitions = NULL; /* do not marshal */
+
+/**
+ * The libgamma CRTC states
+ */
+libgamma_crtc_state_t *restrict crtcs = NULL; /* do not marshal */
+
+/**
+ * Preserve gamma ramps at priority 0?
+ */
+int preserve = 0;
+
+
+/**
+ * As part of a state dump, dump one or two gamma ramp-trios
+ *
+ * @param left The left ramps
+ * @param right The right ramps
+ * @param depth The gamma ramp type/depth
+ * @param have_right Print right ramps?
+ * @param indent Print indent
+ */
+static void
+ramps_dump(union gamma_ramps *left, union gamma_ramps *right, signed depth, int have_right, const char *indent)
+{
+#define STRINGISE(SIDE, CH, N, BUF)\
+ do {\
+ if (!SIDE || !SIDE->u8.CH) {\
+ strcpy(BUF, "null");\
+ } else if (i < N) {\
+ switch (depth) {\
+ case -2: snprintf(BUF, sizeof(BUF), "%lf", SIDE->d.CH[i]); break;\
+ case -1: snprintf(BUF, sizeof(BUF), "%f", (double)(SIDE->f.CH[i])); break;\
+ case 8: snprintf(BUF, sizeof(BUF), "%02" PRIx8, SIDE->u8.CH[i]); break;\
+ case 16: snprintf(BUF, sizeof(BUF), "%04" PRIx16, SIDE->u16.CH[i]); break;\
+ case 32: snprintf(BUF, sizeof(BUF), "%08" PRIx32, SIDE->u32.CH[i]); break;\
+ case 64: snprintf(BUF, sizeof(BUF), "%16" PRIx64, SIDE->u64.CH[i]); break;\
+ default:\
+ strcpy(BUF, "corrupt state");\
+ break;\
+ }\
+ }\
+ } while (0)
+
+ char lr[320], lg[320], lb[320], rr[320], rg[320], rb[320];
+ size_t rn = left ? left->u8.red_size : right ? right->u8.red_size : 0;
+ size_t gn = left ? left->u8.green_size : right ? right->u8.green_size : 0;
+ size_t bn = left ? left->u8.blue_size : right ? right->u8.blue_size : 0;
+ size_t i, n = rn > gn ? rn : gn;
+ n = n > bn ? n : bn;
+
+ for (i = 0; i < n; i++) {
+ *lr = *lg = *lb = *rr = *rg = *rb = '\0';
+
+ STRINGISE(left, red, rn, lr);
+ STRINGISE(left, green, gn, lg);
+ STRINGISE(left, blue, bn, lb);
+
+ if (have_right) {
+ STRINGISE(right, red, rn, rr);
+ STRINGISE(right, green, gn, rg);
+ STRINGISE(right, blue, bn, rb);
+ }
+
+ if (have_right)
+ fprintf(stderr, "%s%zu: %s, %s, %s :: %s, %s, %s\n", indent, i, lr, lg, lb, rr, rg, rb);
+ else
+ fprintf(stderr, "%s%zu: %s, %s, %s\n", indent, i, lr, lg, lb);
+ }
+}
+
+
+/**
+ * Dump the state to stderr
+ */
+void
+state_dump(void)
+{
+ size_t i, j;
+ struct output *restrict out;
+ const char *str;
+ struct filter *restrict filter;
+ union gamma_ramps left;
+ size_t depth;
+
+ fprintf(stderr, "argv0: %s\n", argv0 ? argv0 : "(null)");
+ fprintf(stderr, "Realpath of argv0: %s\n", argv0_real ? argv0_real : "(null)");
+ fprintf(stderr, "Calibrations preserved: %s\n", preserve ? "yes" : "no");
+ fprintf(stderr, "Connected: %s\n", connected ? "yes" : "no");
+ fprintf(stderr, "Socket FD: %i\n", socketfd);
+ fprintf(stderr, "Re-execution pending: %s\n", reexec ? "yes" : "no");
+ fprintf(stderr, "Termination pending: %s\n", terminate ? "yes" : "no");
+ if (0 <= connection && connection <= 2)
+ fprintf(stderr, "Pending connection change: %s\n",
+ connection == 0 ? "none" : connection == 1 ? "disconnect" : "reconnect");
+ else
+ fprintf(stderr, "Pending connection change: %i (CORRUPT STATE)\n", connection);
+ fprintf(stderr, "Adjustment method: %i\n", method);
+ fprintf(stderr, "Site name: %s\n", sitename ? sitename : "(automatic)");
+ fprintf(stderr, "Clients:\n");
+ fprintf(stderr, " Next empty slot: %zu\n", connections_ptr);
+ fprintf(stderr, " Initialised slots: %zu\n", connections_used);
+ fprintf(stderr, " Allocated slots: %zu\n", connections_alloc);
+ if (!connections) {
+ fprintf(stderr, " File descriptor array is null\n");
+ } else {
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] < 0) {
+ fprintf(stderr, " Slot %zu: empty\n", i);
+ continue;
+ }
+ fprintf(stderr, " Slot %zu:\n", i);
+ fprintf(stderr, " File descriptor: %i\n", connections[i]);
+ if (!inbound) {
+ fprintf(stderr, " Inbound message array is null\n");
+ } else {
+ fprintf(stderr, " Inbound message:\n");
+ fprintf(stderr, " Header array: %s\n", inbound[i].headers ? "non-null" : "null");
+ fprintf(stderr, " Headers: %zu\n", inbound[i].header_count);
+ fprintf(stderr, " Payload buffer: %s\n", inbound[i].payload ? "non-null" : "null");
+ fprintf(stderr, " Payload size: %zu\n", inbound[i].payload_size);
+ fprintf(stderr, " Payload write pointer: %zu\n", inbound[i].payload_ptr);
+ fprintf(stderr, " Message buffer: %s\n", inbound[i].buffer ? "non-null" : "null");
+ fprintf(stderr, " Message buffer size: %zu\n", inbound[i].buffer_size);
+ fprintf(stderr, " Message buffer write pointer: %zu\n", inbound[i].buffer_ptr);
+ fprintf(stderr, " Read stage: %i\n", inbound[i].stage);
+ }
+ if (!outbound) {
+ fprintf(stderr, " Outbound message array is null\n");
+ } else {
+ fprintf(stderr, " Ring buffer: %s\n", outbound[i].buffer ? "non-null" : "null");
+ fprintf(stderr, " Head: %zu\n", outbound[i].end);
+ fprintf(stderr, " Tail: %zu\n", outbound[i].start);
+ fprintf(stderr, " Size: %zu\n", outbound[i].size);
+ }
+ }
+ }
+ fprintf(stderr, "Partition array: %s\n", partitions ? "non-null" : "null");
+ fprintf(stderr, "CRTC array: %s\n", crtcs ? "non-null" : "null");
+ fprintf(stderr, "Output:\n");
+ fprintf(stderr, " Output count: %zu\n", outputs_n);
+ if (!outputs) {
+ fprintf(stderr, " Output array is null\n");
+ } else {
+ for (i = 0; i < outputs_n; i++) {
+ out = outputs + i;
+ fprintf(stderr, " Output %zu:\n", i);
+ fprintf(stderr, " Depth: %i (%s)\n", out->depth,
+ out->depth == -1 ? "float" :
+ out->depth == -2 ? "double" :
+ out->depth == 8 ? "uint8_t" :
+ out->depth == 16 ? "uint16_t" :
+ out->depth == 32 ? "uint32_t" :
+ out->depth == 64 ? "uint64_t" : "CORRUPT STATE");
+ fprintf(stderr, " Gamma supported: %s (%u)\n",
+ out->supported == LIBGAMMA_YES ? "yes" :
+ out->supported == LIBGAMMA_NO ? "no" :
+ out->supported == LIBGAMMA_MAYBE ? "maybe" :
+ "CORRUPT STATE", out->supported);
+ fprintf(stderr, " Name is EDID: %s\n", out->name_is_edid ? "yes" : "no");
+ switch (out->colourspace) {
+ case COLOURSPACE_UNKNOWN: str = "unknown"; break;
+ case COLOURSPACE_SRGB: str = "sRGB with explicit gamut"; break;
+ case COLOURSPACE_SRGB_SANS_GAMUT: str = "sRGB with implicit gamut (actually illegal)"; break;
+ case COLOURSPACE_RGB: str = "RGB other than sRGB, with unknown gamut"; break;
+ case COLOURSPACE_RGB_SANS_GAMUT: str = "RGB other than sRGB, with listed gamut"; break;
+ case COLOURSPACE_NON_RGB: str = "Non-RGB multicolour"; break;
+ case COLOURSPACE_GREY: str = "Monochrome or singlecolour scale"; break;
+ default: str = "CORRUPT STATE"; break;
+ }
+ fprintf(stderr, " Colourspace: %s (%u)\n", str, out->colourspace);
+ if (out->colourspace == COLOURSPACE_SRGB || out->colourspace == COLOURSPACE_RGB) {
+ fprintf(stderr, " Red (x, y): (%u / 1024, %u / 1024)\n", out->red_x, out->red_y);
+ fprintf(stderr, " Green (x, y): (%u / 1024, %u / 1024)\n", out->green_x, out->green_y);
+ fprintf(stderr, " Blue (x, y): (%u / 1024, %u / 1024)\n", out->blue_x, out->blue_y);
+ fprintf(stderr, " White (x, y): (%u / 1024, %u / 1024)\n", out->white_x, out->white_y);
+ if (out->colourspace == COLOURSPACE_SRGB) {
+ fprintf(stderr, " Expected red (x, y): (655 / 1024, 338 / 1024)\n");
+ fprintf(stderr, " Expected green (x, y): (307 / 1024, 614 / 1024)\n");
+ fprintf(stderr, " Expected blue (x, y): (154 / 1024, 61 / 1024)\n");
+ fprintf(stderr, " Expected white (x, y): (320 / 1024, 337 / 1024)\n");
+ }
+ }
+ if (out->supported) {
+ fprintf(stderr, " Gamma ramp size:\n");
+ fprintf(stderr, " Red: %zu stops\n", out->red_size);
+ fprintf(stderr, " Green: %zu stops\n", out->green_size);
+ fprintf(stderr, " Blue: %zu stops\n", out->blue_size);
+ fprintf(stderr, " Total: %zu bytes\n", out->ramps_size);
+ fprintf(stderr, " Name: %s\n", out->name ? out->name : "(null)");
+ fprintf(stderr, " CRTC state: %s\n", out->crtc ? "non-null" : "null");
+ fprintf(stderr, " Saved gamma ramps (stop: red, green, blue):\n");
+ ramps_dump(&out->saved_ramps, NULL, out->depth, 0, " ");
+ fprintf(stderr, " Filter table:\n");
+ fprintf(stderr, " Filter count: %zu\n", out->table_size);
+ fprintf(stderr, " Slots allocated: %zu\n", out->table_alloc);
+ if (out->table_size > 0) {
+ if (!out->table_filters)
+ fprintf(stderr, " Filter table is null\n");
+ if (!out->table_sums)
+ fprintf(stderr, " Result table is null\n");
+ }
+ for (j = 0; j < out->table_size; j++) {
+ filter = out->table_filters ? out->table_filters + j : NULL;
+ fprintf(stderr, " Filter %zu:\n", j);
+ if (filter) {
+ if (filter->lifespan == LIFESPAN_UNTIL_DEATH)
+ fprintf(stderr, " Client FD: %i\n", filter->client);
+ switch (filter->lifespan) {
+ case LIFESPAN_REMOVE: str = "remove (ILLEGAL STATE)"; break;
+ case LIFESPAN_UNTIL_REMOVAL: str = "until-removal"; break;
+ case LIFESPAN_UNTIL_DEATH: str = "until-death"; break;
+ default: str = "CORRUPT STATE"; break;
+ }
+ fprintf(stderr, " Lifespan: %s (%u)\n", str, filter->lifespan);
+ fprintf(stderr, " Priority: %"PRIi64"\n", filter->priority);
+ fprintf(stderr, " Class: %s\n", filter->class ? filter->class : "(null)");
+ str = "yes";
+ if (!filter->class)
+ str = "no, is NULL";
+ else if (strchr(filter->class, '\n'))
+ str = "no, contains LF";
+ else if (!strstr(filter->class, "::"))
+ str = "no, does not contain \"::\"";
+ else if (!strstr(strstr(filter->class, "::") + 2, "::"))
+ str = "no, contains only one \"::\"";
+ else if (verify_utf8(filter->class) < 0)
+ str = "no, not UTF-8";
+ fprintf(stderr, " Class legal: %s\n", str);
+ if (!filter->ramps && filter->lifespan != LIFESPAN_REMOVE)
+ fprintf(stderr, " Ramps are NULL\n");
+ }
+ if (filter ? filter->lifespan != LIFESPAN_REMOVE : !!out->table_sums) {
+ switch (out->depth) {
+ case -2:
+ depth = sizeof(double);
+ break;
+ case -1:
+ depth = sizeof(float);
+ break;
+ case 8: case 16: case 32: case 64:
+ depth = (size_t)(out->depth) / 8;
+ break;
+ default:
+ goto corrupt_depth;
+ }
+ if (filter && filter->ramps) {
+ left.u8.red_size = out->red_size;
+ left.u8.green_size = out->green_size;
+ left.u8.blue_size = out->blue_size;
+ left.u8.red = filter->ramps;
+ left.u8.green = left.u8.red + out->red_size * depth;
+ left.u8.blue = left.u8.green + out->green_size * depth;
+ }
+ fprintf(stderr, " Ramps (stop: filter red, green, blue :: "
+ "composite red, geen, blue):\n");
+ ramps_dump((filter && filter->ramps) ? &left : NULL,
+ out->table_sums ? out->table_sums + j : NULL,
+ out->depth, 1, " ");
+ corrupt_depth:;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * Destroy the state
+ */
+void
+state_destroy(void)
+{
+ size_t i;
+
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] >= 0) {
+ message_destroy(inbound + i);
+ ring_destroy(outbound + i);
+ }
+ }
+ free(inbound);
+ free(outbound);
+ free(connections);
+
+ if (outputs)
+ for (i = 0; i < outputs_n; i++)
+ output_destroy(outputs + i);
+ free(outputs);
+
+ if (crtcs)
+ for (i = 0; i < outputs_n; i++)
+ libgamma_crtc_destroy(crtcs + i);
+ free(crtcs);
+
+ if (partitions)
+ for (i = 0; i < site.partitions_available; i++)
+ libgamma_partition_destroy(partitions + i);
+ free(partitions);
+
+ libgamma_site_destroy(&site);
+ free(sitename);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal the state
+ *
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how many bytes
+ * this buffer needs
+ * @return The number of marshalled bytes
+ */
+size_t
+state_marshal(void *restrict buf)
+{
+ size_t off = 0, i, n;
+ char *restrict bs = buf;
+
+ if (!argv0_real) {
+ if (bs)
+ bs[off] = '\0';
+ off += 1;
+ } else {
+ n = strlen(argv0_real) + 1;
+ if (bs)
+ memcpy(&bs[off], argv0_real, n);
+ off += n;
+ }
+
+ if (bs)
+ *(size_t *)&bs[off] = outputs_n;
+ off += sizeof(size_t);
+
+ for (i = 0; i < outputs_n; i++)
+ off += output_marshal(outputs + i, bs ? &bs[off] : NULL);
+
+ if (bs)
+ *(int *)&bs[off] = socketfd;
+ off += sizeof(int);
+
+ if (bs)
+ *(sig_atomic_t *)&bs[off] = connection;
+ off += sizeof(sig_atomic_t);
+
+ if (bs)
+ *(int *)&bs[off] = connected;
+ off += sizeof(int);
+
+ if (bs)
+ *(size_t *)&bs[off] = connections_ptr;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = connections_used;
+ off += sizeof(size_t);
+
+ if (bs)
+ memcpy(&bs[off], connections, connections_used * sizeof(*connections));
+ off += connections_used * sizeof(*connections);
+
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] >= 0) {
+ off += message_marshal(&inbound[i], bs ? &bs[off] : NULL);
+ off += ring_marshal(&outbound[i], bs ? &bs[off] : NULL);
+ }
+ }
+
+ if (bs)
+ *(int *)&bs[off] = method;
+ off += sizeof(int);
+
+ if (bs)
+ *(int *)&bs[off] = sitename != NULL;
+ off += sizeof(int);
+ if (sitename) {
+ n = strlen(sitename) + 1;
+ if (bs)
+ memcpy(&bs[off], sitename, n);
+ off += n;
+ }
+
+ if (bs)
+ *(int *)&bs[off] = preserve;
+ off += sizeof(int);
+
+ return off;
+}
+
+
+/**
+ * Unmarshal the state
+ *
+ * @param buf Buffer for the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+state_unmarshal(const void *restrict buf)
+{
+ size_t off = 0, i, n;
+ const char *restrict bs = buf;
+
+ connections = NULL;
+ inbound = NULL;
+
+ if (bs[off]) {
+ n = strlen(&bs[off]) + 1;
+ if (!(argv0_real = memdup(&bs[off], n)))
+ return 0;
+ off += n;
+ } else {
+ off += 1;
+ }
+
+ outputs_n = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ outputs = calloc(outputs_n, sizeof(*outputs));
+ if (!outputs)
+ return 0;
+
+ for (i = 0; i < outputs_n; i++) {
+ off += n = output_unmarshal(outputs + i, &bs[off]);
+ if (!n)
+ return 0;
+ }
+
+ socketfd = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ connection = *(const sig_atomic_t *)&bs[off];
+ off += sizeof(sig_atomic_t);
+
+ connected = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ connections_ptr = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ connections_alloc = connections_used = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ if (connections_used > 0) {
+ connections = memdup(&bs[off], connections_used * sizeof(*connections));
+ if (!connections)
+ return 0;
+ off += connections_used * sizeof(*connections);
+
+ inbound = malloc(connections_used * sizeof(*inbound));
+ if (!inbound)
+ return 0;
+
+ outbound = malloc(connections_used * sizeof(*outbound));
+ if (!outbound)
+ return 0;
+ }
+
+ for (i = 0; i < connections_used; i++) {
+ if (connections[i] >= 0) {
+ off += n = message_unmarshal(&inbound[i], &bs[off]);
+ if (!n)
+ return 0;
+ off += n = ring_unmarshal(&outbound[i], &bs[off]);
+ if (!n)
+ return 0;
+ }
+ }
+
+ method = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ if (*(const int *)&bs[off]) {
+ off += sizeof(int);
+ n = strlen(&bs[off]) + 1;
+ if (!(sitename = memdup(&bs[off], n)))
+ return 0;
+ off += n;
+ } else {
+ off += sizeof(int);
+ }
+
+ preserve = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/src/state.h b/state.h
index 1cbe482..f707c81 100644
--- a/src/state.h
+++ b/state.h
@@ -1,60 +1,39 @@
-/**
- * coopgammad -- Cooperative gamma server
- * Copyright (C) 2016 Mattias Andrée (maandree@kth.se)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+/* See LICENSE file for copyright and license details. */
#ifndef STATE_H
#define STATE_H
-
-#include "types/message.h"
-#include "types/ring.h"
-#include "types/output.h"
+#include "types-message.h"
+#include "types-ring.h"
+#include "types-output.h"
#include <libgamma.h>
#include <stddef.h>
#include <signal.h>
-
-
#ifndef GCC_ONLY
# if defined(__GNUC__) && !defined(__clang__)
-# define GCC_ONLY(...) __VA_ARGS__
+# define GCC_ONLY(...) __VA_ARGS__
# else
-# define GCC_ONLY(...) /* nothing */
+# define GCC_ONLY(...) /* nothing */
# endif
#endif
-
-
/**
* The name of the process
*/
-extern char* restrict argv0;
+extern char *restrict argv0;
/**
* The real pathname of the process's binary,
* `NULL` if `argv0` is satisfactory
*/
-extern char* restrict argv0_real;
+extern char *restrict argv0_real;
/**
* Array of all outputs
*/
-extern struct output* restrict outputs;
+extern struct output *restrict outputs;
/**
* The nubmer of elements in `outputs`
@@ -92,7 +71,7 @@ extern volatile sig_atomic_t connection;
* Unused slots, with index less than `connections_used`,
* should have the value -1 (negative)
*/
-extern int* restrict connections;
+extern int *restrict connections;
/**
* The number of elements allocated for `connections`
@@ -112,12 +91,12 @@ extern size_t connections_used;
/**
* The clients' connections' inbound-message buffers
*/
-extern struct message* restrict inbound;
+extern struct message *restrict inbound;
/**
* The clients' connections' outbound-message buffers
*/
-extern struct ring* restrict outbound;
+extern struct ring *restrict outbound;
/**
* Is the server connect to the display?
@@ -134,7 +113,7 @@ extern int method;
/**
* The site's name, may be `NULL`
*/
-extern char* restrict sitename;
+extern char *restrict sitename;
/**
* The libgamma site state
@@ -144,20 +123,18 @@ extern libgamma_site_state_t site;
/**
* The libgamma partition states
*/
-extern libgamma_partition_state_t* restrict partitions;
+extern libgamma_partition_state_t *restrict partitions;
/**
* The libgamma CRTC states
*/
-extern libgamma_crtc_state_t* restrict crtcs;
+extern libgamma_crtc_state_t *restrict crtcs;
/**
* Preserve gamma ramps at priority 0?
*/
extern int preserve;
-
-
/**
* Dump the state to stderr
*/
@@ -176,7 +153,7 @@ void state_destroy(void);
* this buffer needs
* @return The number of marshalled bytes
*/
-size_t state_marshal(void* restrict buf);
+size_t state_marshal(void *restrict buf);
/**
* Unmarshal the state
@@ -184,9 +161,7 @@ size_t state_marshal(void* restrict buf);
* @param buf Buffer for the marshalled data
* @return The number of unmarshalled bytes, 0 on error
*/
-GCC_ONLY(__attribute__((nonnull)))
-size_t state_unmarshal(const void* restrict buf);
-
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t state_unmarshal(const void *restrict buf);
#endif
-
diff --git a/types-filter.c b/types-filter.c
new file mode 100644
index 0000000..7fbebbd
--- /dev/null
+++ b/types-filter.c
@@ -0,0 +1,125 @@
+/* See LICENSE file for copyright and license details. */
+#include "types-filter.h"
+#include "util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * Free all resources allocated to a filter.
+ * The allocation of `filter` itself is not freed.
+ *
+ * @param this The filter
+ */
+void
+filter_destroy(struct filter *restrict this)
+{
+ free(this->class);
+ free(this->ramps);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a filter
+ *
+ * @param this The filter
+ * @param buf Output buffer for the marshalled filter,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of `this->ramps`
+ * @return The number of marshalled byte
+ */
+size_t
+filter_marshal(const struct filter *restrict this, void *restrict buf, size_t ramps_size)
+{
+ size_t off = 0, n;
+ char nonnulls = 0;
+ char *restrict bs = buf;
+
+ if (bs) {
+ if (this->class)
+ nonnulls |= 1;
+ if (this->ramps)
+ nonnulls |= 2;
+ bs[off] = nonnulls;
+ }
+ off += 1;
+
+ if (bs)
+ *(int64_t *)&bs[off] = this->priority;
+ off += sizeof(int64_t);
+
+ if (bs)
+ *(enum lifespan *)&bs[off] = this->lifespan;
+ off += sizeof(enum lifespan);
+
+ if (this->class) {
+ n = strlen(this->class) + 1;
+ if (bs)
+ memcpy(&bs[off], this->class, n);
+ off += n;
+ }
+
+ if (this->ramps) {
+ if (bs)
+ memcpy(&bs[off], this->ramps, ramps_size);
+ off += ramps_size;
+ }
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a filter
+ *
+ * @param this Output for the filter
+ * @param buf Buffer with the marshalled filter
+ * @param ramps_size The byte-size of `this->ramps`
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+filter_unmarshal(struct filter *restrict this, const void *restrict buf, size_t ramps_size)
+{
+ size_t off = 0, n;
+ char nonnulls = 0;
+ const char *restrict bs = buf;
+
+ nonnulls = bs[off];
+ off += 1;
+
+ this->class = NULL;
+ this->ramps = NULL;
+
+ this->priority = *(const int64_t *)&bs[off];
+ off += sizeof(int64_t);
+
+ this->lifespan = *(const enum lifespan *)&bs[off];
+ off += sizeof(enum lifespan);
+
+ if (nonnulls & 1) {
+ n = strlen(&bs[off]) + 1;
+ if (!(this->class = memdup(&bs[off], n)))
+ goto fail;
+ off += n;
+ }
+
+ if (nonnulls & 2) {
+ if (!(this->ramps = memdup(&bs[off], ramps_size)))
+ goto fail;
+ off += ramps_size;
+ }
+
+ return off;
+
+fail:
+ free(this->class);
+ free(this->ramps);
+ return 0;
+}
diff --git a/types-filter.h b/types-filter.h
new file mode 100644
index 0000000..22f4783
--- /dev/null
+++ b/types-filter.h
@@ -0,0 +1,108 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TYPES_FILTER_H
+#define TYPES_FILTER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * The lifespan of a filter
+ */
+enum lifespan {
+ /**
+ * The filter should be removed now
+ */
+ LIFESPAN_REMOVE = 0,
+
+ /**
+ * The filter should be applied
+ * until it is explicitly removed
+ */
+ LIFESPAN_UNTIL_REMOVAL = 1,
+
+ /**
+ * The filter should be applied
+ * until the client exists
+ */
+ LIFESPAN_UNTIL_DEATH = 2
+};
+
+/**
+ * Information about a filter
+ */
+struct filter {
+ /**
+ * The client that applied it. This need not be
+ * set unless `.lifespan == LIFESPAN_UNTIL_DEATH`
+ * and unless the process itself added this.
+ * This is the file descriptor of the client's
+ * connection.
+ */
+ int client;
+
+ /**
+ * The lifespan of the filter
+ */
+ enum lifespan lifespan;
+
+ /**
+ * The priority of the filter
+ */
+ int64_t priority;
+
+ /**
+ * Identifier for the filter
+ */
+ char *class;
+
+ /**
+ * The gamma ramp adjustments for the filter.
+ * This is raw binary data. `NULL` iff
+ * `lifespan == LIFESPAN_REMOVE`.
+ */
+ void *ramps;
+};
+
+/**
+ * Free all resources allocated to a filter.
+ * The allocation of `filter` itself is not freed.
+ *
+ * @param this The filter
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void filter_destroy(struct filter *restrict this);
+
+/**
+ * Marshal a filter
+ *
+ * @param this The filter
+ * @param buf Output buffer for the marshalled filter,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of `filter->ramps`
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+size_t filter_marshal(const struct filter *restrict this, void *restrict buf, size_t ramps_size);
+
+/**
+ * Unmarshal a filter
+ *
+ * @param this Output for the filter, `.red_size`, `.green_size`,
+ * and `.blue_size` must already be set
+ * @param buf Buffer with the marshalled filter
+ * @param ramps_size The byte-size of `filter->ramps`
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t filter_unmarshal(struct filter *restrict this, const void *restrict buf, size_t ramps_size);
+
+#endif
diff --git a/types-message.c b/types-message.c
new file mode 100644
index 0000000..de5644d
--- /dev/null
+++ b/types-message.c
@@ -0,0 +1,561 @@
+/* See LICENSE file for copyright and license details. */
+#include "types-message.h"
+#include "util.h"
+
+#include <sys/socket.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * Initialise a message slot so that it can
+ * be used by to read messages
+ *
+ * @param this Memory slot in which to store the new message
+ * @return Non-zero on error, `errno` will be set accordingly
+ */
+int
+message_initialise(struct message *restrict this)
+{
+ this->headers = NULL;
+ this->header_count = 0;
+ this->payload = NULL;
+ this->payload_size = 0;
+ this->payload_ptr = 0;
+ this->buffer_size = 128;
+ this->buffer_ptr = 0;
+ this->stage = 0;
+ this->buffer = malloc(this->buffer_size);
+ if (!this->buffer)
+ return -1;
+ return 0;
+}
+
+
+/**
+ * Release all resources in a message, should
+ * be done even if initialisation fails
+ *
+ * @param this The message
+ */
+void
+message_destroy(struct message *restrict this)
+{
+ size_t i;
+
+ if (this->headers) {
+ for (i = 0; i < this->header_count; i++)
+ free(this->headers[i]);
+ free(this->headers);
+ }
+ free(this->payload);
+ free(this->buffer);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a message for state serialisation
+ *
+ * @param this The message
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+size_t
+message_marshal(const struct message *restrict this, void *restrict buf)
+{
+ size_t i, n, off = 0;
+ char *bs = buf;
+
+ if (bs)
+ *(size_t *)&bs[off] = this->header_count;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->payload_size;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->payload_ptr;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->buffer_ptr;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(int *)&bs[off] = this->stage;
+ off += sizeof(int);
+
+ for (i = 0; i < this->header_count; i++) {
+ n = strlen(this->headers[i]) + 1;
+ if (bs)
+ memcpy(&bs[off], this->headers[i], n);
+ off += n;
+ }
+
+ if (bs)
+ memcpy(&bs[off], this->payload, this->payload_ptr);
+ off += this->payload_ptr;
+
+ if (bs)
+ memcpy(&bs[off], this->buffer, this->buffer_ptr);
+ off += this->buffer_ptr;
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a message for state deserialisation
+ *
+ * @param this Memory slot in which to store the new message
+ * @param buf In buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+message_unmarshal(struct message *restrict this, const void *restrict buf)
+{
+ size_t i, n, off = 0, header_count;
+ const char *bs = buf;
+
+ this->header_count = 0;
+
+ header_count = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->payload_size = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->payload_ptr = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->buffer_size = this->buffer_ptr = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->stage = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ /* Make sure that the pointers are NULL so that they are
+ not freed without being allocated when the message is
+ destroyed if this function fails. */
+ this->headers = NULL;
+ this->payload = NULL;
+ this->buffer = NULL;
+
+ /* To 2-power-multiple of 128 bytes. */
+ this->buffer_size >>= 7;
+ if (!this->buffer_size) {
+ this->buffer_size = 1;
+ } else {
+ this->buffer_size -= 1;
+ this->buffer_size |= this->buffer_size >> 1;
+ this->buffer_size |= this->buffer_size >> 2;
+ this->buffer_size |= this->buffer_size >> 4;
+ this->buffer_size |= this->buffer_size >> 8;
+ this->buffer_size |= this->buffer_size >> 16;
+#if SIZE_MAX == UINT64_MAX
+ this->buffer_size |= this->buffer_size >> 32;
+#endif
+ this->buffer_size += 1;
+ }
+ this->buffer_size <<= 7;
+
+ /* Allocate header list, payload and read buffer. */
+
+ if (header_count > 0)
+ if (!(this->headers = malloc(header_count * sizeof(char*))))
+ goto fail;
+
+ if (this->payload_size > 0)
+ if (!(this->payload = malloc(this->payload_size)))
+ goto fail;
+
+ if (!(this->buffer = malloc(this->buffer_size)))
+ goto fail;
+
+ /* Fill the header list, payload and read buffer. */
+
+ for (i = 0; i < header_count; i++) {
+ n = strlen(&bs[off]) + 1;
+ this->headers[i] = memdup(&bs[off], n);
+ if (!this->headers[i])
+ goto fail;
+ off += n;
+ this->header_count++;
+ }
+
+ memcpy(this->payload, &bs[off], this->payload_ptr);
+ off += this->payload_ptr;
+
+ memcpy(this->buffer, &bs[off], this->buffer_ptr);
+ off += this->buffer_ptr;
+
+ return off;
+
+fail:
+ return 0;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+
+/**
+ * Extend the header list's allocation
+ *
+ * @param this The message
+ * @param extent The number of additional entries
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+extend_headers(struct message *restrict this, size_t extent)
+{
+ char **new = realloc(this->headers, (this->header_count + extent) * sizeof(char *));
+ if (!new)
+ return -1;
+ this->headers = new;
+ return 0;
+}
+
+
+/**
+ * Extend the read buffer by way of doubling
+ *
+ * @param this The message
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+extend_buffer(struct message *restrict this)
+{
+ char *restrict new = realloc(this->buffer, (this->buffer_size << 1) * sizeof(char));
+ if (!new)
+ return -1;
+ this->buffer = new;
+ this->buffer_size <<= 1;
+ return 0;
+}
+
+
+/**
+ * Reset the header list and the payload
+ *
+ * @param this The message
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static void
+reset_message(struct message *restrict this)
+{
+ size_t i;
+ if (this->headers) {
+ for (i = 0; i < this->header_count; i++)
+ free(this->headers[i]);
+ free(this->headers);
+ this->headers = NULL;
+ }
+ this->header_count = 0;
+
+ free(this->payload);
+ this->payload = NULL;
+ this->payload_size = 0;
+ this->payload_ptr = 0;
+}
+
+
+/**
+ * Read the headers the message and determine, and store, its payload's length
+ *
+ * @param this The message
+ * @return Zero on success, negative on error (malformated message: unrecoverable state)
+ */
+GCC_ONLY(__attribute__((__pure__, __nonnull__)))
+static int
+get_payload_length(struct message *restrict this)
+{
+ char *header;
+ size_t i;
+
+ for (i = 0; i < this->header_count; i++) {
+ if (strstr(this->headers[i], "Length: ") == this->headers[i]) {
+ /* Store the message length. */
+ header = this->headers[i] + strlen("Length: ");
+ this->payload_size = (size_t)atol(header);
+
+ /* Do not except a length that is not correctly formated. */
+ for (; *header; header++)
+ if (*header < '0' || '9' < *header)
+ return -2; /* Malformated value, enters unrecoverable state. */
+
+ /* Stop searching for the ‘Length’ header, we have found and parsed it. */
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Verify that a header is correctly formatted
+ *
+ * @param header The header, must be NUL-terminated
+ * @param length The length of the header
+ * @return Zero if valid, negative if invalid (malformated message: unrecoverable state)
+ */
+GCC_ONLY(__attribute__((__pure__, __nonnull__)))
+static int
+validate_header(const char *restrict header, size_t length)
+{
+ char *restrict p = memchr(header, ':', length * sizeof(char));
+
+ if (verify_utf8(header) < 0) {
+ /* Either the string is not UTF-8, or your are under an UTF-8 attack,
+ let's just call this unrecoverable because the client will not correct. */
+ return -2;
+ }
+
+ if (!p || /* Buck you, rawmemchr should not segfault the program. */
+ p[1] != ' ') /* Also an invalid format. ' ' is mandated after the ':'. */
+ return -2;
+
+ return 0;
+}
+
+
+/**
+ * Remove the beginning of the read buffer
+ *
+ * @param this The message
+ * @param length The number of characters to remove
+ * @param update_ptr Whether to update the buffer pointer
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static void
+unbuffer_beginning(struct message *restrict this, size_t length, int update_ptr)
+{
+ memmove(this->buffer, &this->buffer[length], (this->buffer_ptr - length) * sizeof(char));
+ if (update_ptr)
+ this->buffer_ptr -= length;
+}
+
+
+/**
+ * Remove the header–payload delimiter from the buffer,
+ * get the payload's size and allocate the payload
+ *
+ * @param this The message
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+initialise_payload(struct message *restrict this)
+{
+ /* Remove the \n (end of empty line) we found from the buffer. */
+ unbuffer_beginning(this, 1, 1);
+
+ /* Get the length of the payload. */
+ if (get_payload_length(this) < 0)
+ return -2; /* Malformated value, enters unrecoverable state. */
+
+ /* Allocate the payload buffer. */
+ if (this->payload_size > 0) {
+ this->payload = malloc(this->payload_size);
+ if (!this->payload)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Create a header from the buffer and store it
+ *
+ * @param this The message
+ * @param length The length of the header, including LF-termination
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+store_header(struct message *restrict this, size_t length)
+{
+ char *restrict header;
+
+ /* Allocate the header. */
+ header = malloc(length); /* Last char is a LF, which is substituted with NUL. */
+ if (!header)
+ return -1;
+ /* Copy the header data into the allocated header, */
+ memcpy(header, this->buffer, length * sizeof(char));
+ /* and NUL-terminate it. */
+ header[length - 1] = '\0';
+
+ /* Remove the header data from the read buffer. */
+ unbuffer_beginning(this, length, 1);
+
+ /* Make sure the the header syntax is correct so that
+ the program does not need to care about it. */
+ if (validate_header(header, length)) {
+ free(header);
+ return -2;
+ }
+
+ /* Store the header in the header list. */
+ this->headers[this->header_count++] = header;
+
+ return 0;
+}
+
+
+/**
+ * Continue reading from the socket into the buffer
+ *
+ * @param this The message
+ * @param fd The file descriptor of the socket
+ * @return The return value follows the rules of `message_read`
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static int
+continue_read(struct message *restrict this, int fd)
+{
+ size_t n;
+ ssize_t got;
+ int r;
+
+ /* Figure out how much space we have left in the read buffer. */
+ n = this->buffer_size - this->buffer_ptr;
+
+ /* If we do not have too much left, */
+ if (n < 128) {
+ /* grow the buffer, */
+ if ((r = extend_buffer(this)) < 0)
+ return r;
+
+ /* and recalculate how much space we have left. */
+ n = this->buffer_size - this->buffer_ptr;
+ }
+
+ /* Then read from the socket. */
+ errno = 0;
+ got = recv(fd, this->buffer + this->buffer_ptr, n, 0);
+ this->buffer_ptr += (size_t)(got < 0 ? 0 : got);
+ if (errno)
+ return -1;
+ if (got == 0) {
+ errno = ECONNRESET;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Read the next message from a file descriptor
+ *
+ * @param this Memory slot in which to store the new message
+ * @param fd The file descriptor
+ * @return 0: At least one message is available
+ * -1: Exceptional connection:
+ * EINTR: System call interrupted
+ * EAGAIN: No message is available
+ * EWOULDBLOCK: No message is available
+ * ECONNRESET: Connection closed
+ * Other: Failure
+ * -2: Corrupt message (unrecoverable)
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int
+message_read(struct message *restrict this, int fd)
+{
+ size_t header_commit_buffer = 0;
+ size_t length, need, move;
+ int r;
+ char *p;
+
+ /* If we are at stage 2, we are done and it is time to start over.
+ This is important because the function could have been interrupted. */
+ if (this->stage == 2) {
+ reset_message(this);
+ this->stage = 0;
+ }
+
+ /* Read from file descriptor until we have a full message. */
+ for (;;) {
+ /* Stage 0: headers. */
+ /* Read all headers that we have stored into the read buffer. */
+ while (this->stage == 0 &&
+ ((p = memchr(this->buffer, '\n', this->buffer_ptr * sizeof(char))))) {
+ length = (size_t)(p - this->buffer);
+ if (length) {
+ /* We have found a header. */
+
+ /* On every eighth header found with this function call,
+ we prepare the header list for eight more headers so
+ that it does not need to be reallocated again and again. */
+ if (!header_commit_buffer)
+ if ((r = extend_headers(this, header_commit_buffer = 8)) < 0)
+ return r;
+
+ /* Create and store header. */
+ if ((r = store_header(this, length + 1)) < 0)
+ return r;
+ header_commit_buffer -= 1;
+ } else {
+ /* We have found an empty line, i.e. the end of the headers. */
+
+ /* Remove the header–payload delimiter from the buffer,
+ get the payload's size and allocate the payload. */
+ if ((r = initialise_payload(this)) < 0)
+ return r;
+
+ /* Mark end of stage, next stage is getting the payload. */
+ this->stage = 1;
+ }
+ }
+
+
+ /* Stage 1: payload. */
+ if ((this->stage == 1) && (this->payload_size > 0)) {
+ /* How much of the payload that has not yet been filled. */
+ need = this->payload_size - this->payload_ptr;
+ /* How much we have of that what is needed. */
+ move = this->buffer_ptr < need ? this->buffer_ptr : need;
+
+ /* Copy what we have, and remove it from the the read buffer. */
+ memcpy(&this->payload[this->payload_ptr], this->buffer, move * sizeof(char));
+ unbuffer_beginning(this, move, 1);
+
+ /* Keep track of how much we have read. */
+ this->payload_ptr += move;
+ }
+ if (this->stage == 1 && this->payload_ptr == this->payload_size) {
+ /* If we have filled the payload (or there was no payload),
+ mark the end of this stage, i.e. that the message is
+ complete, and return with success. */
+ this->stage = 2;
+ return 0;
+ }
+
+
+ /* If stage 1 was not completed. */
+
+ /* Continue reading from the socket into the buffer. */
+ if ((r = continue_read(this, fd)) < 0)
+ return r;
+ }
+}
diff --git a/types-message.h b/types-message.h
new file mode 100644
index 0000000..5d479b7
--- /dev/null
+++ b/types-message.h
@@ -0,0 +1,133 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TYPES_MESSAGE_H
+#define TYPES_MESSAGE_H
+
+#include <stddef.h>
+#include <limits.h>
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Message passed between a server and a client
+ */
+struct message {
+ /**
+ * The headers in the message, each element in this list
+ * as an unparsed header, it consists of both the header
+ * name and its associated value, joined by ": ". A header
+ * cannot be `NULL` (unless its memory allocation failed,)
+ * but `headers` itself is `NULL` if there are no headers.
+ * The "Length" header should be included in this list.
+ */
+ char **restrict headers;
+
+ /**
+ * The number of headers in the message
+ */
+ size_t header_count;
+
+ /**
+ * The payload of the message, `NULL` if none (of zero-length)
+ */
+ char *restrict payload;
+
+ /**
+ * The size of the payload
+ */
+ size_t payload_size;
+
+ /**
+ * How much of the payload that has been stored (internal data)
+ */
+ size_t payload_ptr;
+
+ /**
+ * Internal buffer for the reading function (internal data)
+ */
+ char *restrict buffer;
+
+ /**
+ * The size allocated to `buffer` (internal data)
+ */
+ size_t buffer_size;
+
+ /**
+ * The number of bytes used in `buffer` (internal data)
+ */
+ size_t buffer_ptr;
+
+ /**
+ * 0 while reading headers, 1 while reading payload, and 2 when done (internal data)
+ */
+ int stage;
+
+#if INT_MAX != LONG_MAX
+ int padding__;
+#endif
+};
+
+/**
+ * Initialise a message slot so that it can
+ * be used by to read messages
+ *
+ * @param this Memory slot in which to store the new message
+ * @return Non-zero on error, `errno` will be set accordingly
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int message_initialise(struct message *restrict this);
+
+/**
+ * Release all resources in a message, should
+ * be done even if initialisation fails
+ *
+ * @param this The message
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void message_destroy(struct message *restrict this);
+
+/**
+ * Marshal a message for state serialisation
+ *
+ * @param this The message
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+size_t message_marshal(const struct message *restrict this, void *restrict buf);
+
+/**
+ * Unmarshal a message for state deserialisation
+ *
+ * @param this Memory slot in which to store the new message
+ * @param buf In buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t message_unmarshal(struct message *restrict this, const void *restrict buf);
+
+/**
+ * Read the next message from a file descriptor
+ *
+ * @param this Memory slot in which to store the new message
+ * @param fd The file descriptor
+ * @return 0: At least one message is available
+ * -1: Exceptional connection:
+ * EINTR: System call interrupted
+ * EAGAIN: No message is available
+ * EWOULDBLOCK: No message is available
+ * ECONNRESET: Connection closed
+ * Other: Failure
+ * -2: Corrupt message (unrecoverable)
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int message_read(struct message *restrict this, int fd);
+
+#endif
diff --git a/types-output.c b/types-output.c
new file mode 100644
index 0000000..e3296cc
--- /dev/null
+++ b/types-output.c
@@ -0,0 +1,322 @@
+/* See LICENSE file for copyright and license details. */
+#include "types-output.h"
+#include "util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * Free all resources allocated to an output.
+ * The allocation of `output` itself is not freed,
+ * nor is its the libgamma destroyed.
+ *
+ * @param this The output
+ */
+void
+output_destroy(struct output *restrict this)
+{
+ size_t i;
+
+ if (this->supported != LIBGAMMA_NO) {
+ switch (this->depth) {
+ case 8:
+ libgamma_gamma_ramps8_destroy(&this->saved_ramps.u8);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps8_destroy(&this->table_sums[i].u8);
+ break;
+
+ case 16:
+ libgamma_gamma_ramps16_destroy(&this->saved_ramps.u16);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps16_destroy(&this->table_sums[i].u16);
+ break;
+
+ case 32:
+ libgamma_gamma_ramps32_destroy(&this->saved_ramps.u32);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps32_destroy(&this->table_sums[i].u32);
+ break;
+
+ case 64:
+ libgamma_gamma_ramps64_destroy(&this->saved_ramps.u64);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_ramps64_destroy(&this->table_sums[i].u64);
+ break;
+
+ case -1:
+ libgamma_gamma_rampsf_destroy(&this->saved_ramps.f);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_rampsf_destroy(&this->table_sums[i].f);
+ break;
+
+ case -2:
+ libgamma_gamma_rampsd_destroy(&this->saved_ramps.d);
+ for (i = 0; i < this->table_size; i++)
+ libgamma_gamma_rampsd_destroy(&this->table_sums[i].d);
+ break;
+
+ default:
+ break; /* impossible */
+ }
+ }
+
+ for (i = 0; i < this->table_size; i++)
+ filter_destroy(&this->table_filters[i]);
+
+ free(this->table_filters);
+ free(this->table_sums);
+ free(this->name);
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal an output
+ *
+ * @param this The output
+ * @param buf Output buffer for the marshalled output,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+size_t
+output_marshal(const struct output *restrict this, void *restrict buf)
+{
+ size_t off = 0, i, n;
+ char *bs = buf;
+
+ if (bs)
+ *(signed *)&bs[off] = this->depth;
+ off += sizeof(signed);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->red_size;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->green_size;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->blue_size;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->ramps_size;
+ off += sizeof(size_t);
+
+ if (bs)
+ *(enum libgamma_decision *)&bs[off] = this->supported;
+ off += sizeof(enum libgamma_decision);
+
+ if (bs)
+ *(enum colourspace *)&bs[off] = this->colourspace;
+ off += sizeof(enum colourspace);
+
+ if (bs)
+ *(int *)&bs[off] = this->name_is_edid;
+ off += sizeof(int);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->red_x;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->red_y;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->green_x;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->green_y;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->blue_x;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->blue_y;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->white_x;
+ off += sizeof(unsigned);
+
+ if (bs)
+ *(unsigned *)&bs[off] = this->white_y;
+ off += sizeof(unsigned);
+
+ n = strlen(this->name) + 1;
+ if (bs)
+ memcpy(&bs[off], this->name, n);
+ off += n;
+
+ off += gamma_ramps_marshal(&(this->saved_ramps), bs ? &bs[off] : NULL, this->ramps_size);
+
+ if (bs)
+ *(size_t *)&bs[off] = this->table_size;
+ off += sizeof(size_t);
+
+ for (i = 0; i < this->table_size; i++) {
+ off += filter_marshal(this->table_filters + i, bs ? &bs[off] : NULL, this->ramps_size);
+ off += gamma_ramps_marshal(this->table_sums + i, bs ? &bs[off] : NULL, this->ramps_size);
+ }
+
+ return off;
+}
+
+
+/**
+ * Unmarshal an output
+ *
+ * @param this Output for the output
+ * @param buf Buffer with the marshalled output
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+output_unmarshal(struct output *restrict this, const void *restrict buf)
+{
+ size_t off = 0, i, n;
+ const char *bs = buf;
+
+ this->crtc = NULL;
+ this->name = NULL;
+
+ this->depth = *(const signed *)&bs[off];
+ off += sizeof(signed);
+
+ this->red_size = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->green_size = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->blue_size = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->ramps_size = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ this->supported = *(const enum libgamma_decision *)&bs[off];
+ off += sizeof(enum libgamma_decision);
+
+ this->colourspace = *(const enum colourspace *)&bs[off];
+ off += sizeof(enum colourspace);
+
+ this->name_is_edid = *(const int *)&bs[off];
+ off += sizeof(int);
+
+ this->red_x = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->red_y = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->green_x = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->green_y = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->blue_x = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->blue_y = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->white_x = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ this->white_y = *(const unsigned *)&bs[off];
+ off += sizeof(unsigned);
+
+ n = strlen(&bs[off]) + 1;
+ this->name = memdup(&bs[off], n);
+ if (this->name == NULL)
+ return 0;
+ off += n;
+
+ COPY_RAMP_SIZES(&this->saved_ramps.u8, this);
+ off += n = gamma_ramps_unmarshal(&this->saved_ramps, &bs[off], this->ramps_size);
+ if (n == 0)
+ return 0;
+
+ this->table_size = this->table_alloc = *(const size_t*)&bs[off];
+ off += sizeof(size_t);
+ if (this->table_size > 0) {
+ this->table_filters = calloc(this->table_size, sizeof(*this->table_filters));
+ if (!this->table_filters)
+ return 0;
+ this->table_sums = calloc(this->table_size, sizeof(*this->table_sums));
+ if (!this->table_sums)
+ return 0;
+ }
+
+ for (i = 0; i < this->table_size; i++) {
+ off += n = filter_unmarshal(&this->table_filters[i], &bs[off], this->ramps_size);
+ if (!n)
+ return 0;
+ COPY_RAMP_SIZES(&this->table_sums[i].u8, this);
+ off += n = gamma_ramps_unmarshal(&this->table_sums[i], &bs[off], this->ramps_size);
+ if (!n)
+ return 0;
+ }
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+/**
+ * Compare to outputs by the names of their respective CRTC:s
+ *
+ * @param a Return -1 if this one is lower
+ * @param b Return +1 if this one is higher
+ * @return See description of `a` and `b`,
+ * 0 if returned if they are the same
+ */
+int
+output_cmp_by_name(const void *restrict a, const void *restrict b)
+{
+ const struct output *x = a, *y = b;
+ return strcmp(x->name, y->name);
+}
+
+
+/**
+ * Find an output by its name
+ *
+ * @param key The name of the output
+ * @param base The array of outputs
+ * @param n The number of elements in `base`
+ * @return Output find in `base`, `NULL` if not found
+ */
+struct output *
+output_find_by_name(const char *restrict key, struct output *restrict base, size_t n)
+{
+ struct output k;
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+ k.name = (char *)key;
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+ return bsearch(&k, base, n, sizeof(*base), output_cmp_by_name);
+}
diff --git a/types-output.h b/types-output.h
new file mode 100644
index 0000000..ca5bf61
--- /dev/null
+++ b/types-output.h
@@ -0,0 +1,275 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TYPES_OUTPUT_H
+#define TYPES_OUTPUT_H
+
+#include <stddef.h>
+
+#include <libgamma.h>
+
+#include "types-ramps.h"
+#include "types-filter.h"
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Copy the ramp sizes
+ *
+ * This macro supports both `struct output`
+ * and `struct gamma_ramps`
+ *
+ * @param dest The destination
+ * @param src The source
+ */
+#define COPY_RAMP_SIZES(dest, src) \
+ ((dest)->red_size = (src)->red_size, \
+ (dest)->green_size = (src)->green_size, \
+ (dest)->blue_size = (src)->blue_size)
+
+/**
+ * Colour spaces
+ */
+enum colourspace {
+ /**
+ * Unknown
+ */
+ COLOURSPACE_UNKNOWN = 0,
+
+ /**
+ * sRGB with explicit gamut
+ */
+ COLOURSPACE_SRGB = 1,
+
+ /**
+ * sRGB without explicit gamut
+ */
+ COLOURSPACE_SRGB_SANS_GAMUT = 2,
+
+ /**
+ * RGB (but not sRGB) with known gamut
+ */
+ COLOURSPACE_RGB = 3,
+
+ /**
+ * RGB (but not sRGB) without known gamut
+ */
+ COLOURSPACE_RGB_SANS_GAMUT = 4,
+
+ /**
+ * Non-RGB multicolour
+ */
+ COLOURSPACE_NON_RGB = 5,
+
+ /**
+ * Greyscale or monochrome
+ */
+ COLOURSPACE_GREY = 6
+};
+
+/**
+ * Information about an output
+ */
+struct output {
+ /**
+ * -2: double
+ * -1: float
+ * 8: uint8_t
+ * 16: uint16_t
+ * 32: uint32_t
+ * 64: uint64_t
+ */
+ signed depth;
+
+ /**
+ * Whether gamma ramps are supported
+ */
+ enum libgamma_decision supported;
+
+ /**
+ * Whether the name is the EDID
+ */
+ int name_is_edid;
+
+ /**
+ * The monitor's colour space
+ */
+ enum colourspace colourspace;
+
+ /**
+ * The x-value (CIE xyY) of the monitor's
+ * red colour, multiplied by 1024
+ */
+ unsigned red_x;
+
+ /**
+ * The y-value (CIE xyY) of the monitor's
+ * red colour, multiplied by 1024
+ */
+ unsigned red_y;
+
+ /**
+ * The x-value (CIE xyY) of the monitor's
+ * green colour, multiplied by 1024
+ */
+ unsigned green_x;
+
+ /**
+ * The y-value (CIE xyY) of the monitor's
+ * green colour, multiplied by 1024
+ */
+ unsigned green_y;
+
+ /**
+ * The x-value (CIE xyY) of the monitor's
+ * blue colour, multiplied by 1024
+ */
+ unsigned blue_x;
+
+ /**
+ * The y-value (CIE xyY) of the monitor's
+ * blue colour, multiplied by 1024
+ */
+ unsigned blue_y;
+
+ /**
+ * The x-value (CIE xyY) of the monitor's
+ * default white point, multiplied by 1024
+ */
+ unsigned white_x;
+
+ /**
+ * The y-value (CIE xyY) of the monitor's
+ * default white point, multiplied by 1024
+ */
+ unsigned white_y;
+
+ /**
+ * The number of stops in the red gamma ramp
+ */
+ size_t red_size;
+
+ /**
+ * The number of stops in the green gamma ramp
+ */
+ size_t green_size;
+
+ /**
+ * The number of stops in the blue gamma ramp
+ */
+ size_t blue_size;
+
+ /**
+ * `.red_size + .green_size + .blue_size`
+ * multiplied by the byte-size of each stop
+ */
+ size_t ramps_size;
+
+ /**
+ * The name of the output, will be its EDID
+ * if available, otherwise it will be the
+ * index of the partition, followed by a dot
+ * and the index of the CRTC within the
+ * partition, or if a name for the connector
+ * is available: the index of the partition
+ * followed by a dot and the name of the
+ * connector
+ */
+ char *restrict name;
+
+ /**
+ * The libgamma state for the output
+ */
+ libgamma_crtc_state_t *restrict crtc;
+
+ /**
+ * Saved gamma ramps
+ */
+ union gamma_ramps saved_ramps;
+
+ /**
+ * The table of all applied filters
+ */
+ struct filter *restrict table_filters;
+
+ /**
+ * `.table_sums[i]` is the resulting
+ * adjustment made when all filter
+ * from `.table_filters[0]` up to and
+ * including `.table_filters[i]` has
+ * been applied
+ */
+ union gamma_ramps *restrict table_sums;
+
+ /**
+ * The number of elements allocated
+ * for `.table_filters` and for `.table_sums`
+ */
+ size_t table_alloc;
+
+ /**
+ * The number of elements stored in
+ * `.table_filters` and in `.table_sums`
+ */
+ size_t table_size;
+};
+
+/**
+ * Free all resources allocated to an output.
+ * The allocation of `output` itself is not freed,
+ * nor is its the libgamma destroyed.
+ *
+ * @param this The output
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void output_destroy(struct output *restrict this);
+
+/**
+ * Marshal an output
+ *
+ * @param this The output
+ * @param buf Output buffer for the marshalled output,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+size_t output_marshal(const struct output *restrict this, void *restrict buf);
+
+/**
+ * Unmarshal an output
+ *
+ * @param this Output for the output
+ * @param buf Buffer with the marshalled output
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t output_unmarshal(struct output *restrict this, const void *restrict buf);
+
+/**
+ * Compare to outputs by the names of their respective CRTC:s
+ *
+ * @param a Return -1 if this one is lower
+ * @param b Return +1 if this one is higher
+ * @return See description of `a` and `b`,
+ * 0 if returned if they are the same
+ */
+GCC_ONLY(__attribute__((pure, __nonnull__)))
+int output_cmp_by_name(const void *restrict a, const void *restrict b);
+
+/**
+ * Find an output by its name
+ *
+ * @param key The name of the output
+ * @param base The array of outputs
+ * @param n The number of elements in `base`
+ * @return Output find in `base`, `NULL` if not found
+ */
+GCC_ONLY(__attribute__((pure, __nonnull__)))
+struct output *output_find_by_name(const char *restrict key, struct output *restrict base, size_t n);
+
+#endif
diff --git a/types-ramps.c b/types-ramps.c
new file mode 100644
index 0000000..a80f66b
--- /dev/null
+++ b/types-ramps.c
@@ -0,0 +1,86 @@
+/* See LICENSE file for copyright and license details. */
+#include "types-ramps.h"
+
+#include <libclut.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * The name of the process
+ */
+extern char *restrict argv0;
+
+
+/**
+ * Marshal a ramp trio
+ *
+ * @param this The ramps
+ * @param buf Output buffer for the marshalled ramps,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of ramps
+ * @return The number of marshalled byte
+ */
+size_t
+gamma_ramps_marshal(const union gamma_ramps *restrict this, void *restrict buf, size_t ramps_size)
+{
+ if (buf)
+ memcpy(buf, this->u8.red, ramps_size);
+ return ramps_size;
+}
+
+
+/**
+ * Unmarshal a ramp trio
+ *
+ * @param this Output for the ramps, `.red_size`, `.green_size`,
+ * and `.blue_size` must already be set
+ * @param buf Buffer with the marshalled ramps
+ * @param ramps_size The byte-size of ramps
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+gamma_ramps_unmarshal(union gamma_ramps *restrict this, const void *restrict buf, size_t ramps_size)
+{
+ size_t depth = ramps_size / (this->u8.red_size + this->u8.green_size + this->u8.blue_size);
+ int r = 0;
+
+ switch (depth) {
+ case 1:
+ r = libgamma_gamma_ramps8_initialise(&this->u8);
+ break;
+
+ case 2:
+ r = libgamma_gamma_ramps16_initialise(&this->u16);
+ break;
+
+ case 4:
+ r = libgamma_gamma_ramps32_initialise(&this->u32);
+ break;
+
+ case 8:
+ r = libgamma_gamma_ramps64_initialise(&this->u64);
+ break;
+
+ default:
+ if (depth == sizeof(float))
+ r = libgamma_gamma_rampsf_initialise(&this->f);
+ else if (depth == sizeof(double))
+ r = libgamma_gamma_rampsd_initialise(&this->d);
+ else
+ abort();
+ break;
+ }
+
+ if (r) {
+ libgamma_perror(argv0, r);
+ errno = 0;
+ return 0;
+ }
+
+ memcpy(this->u8.red, buf, ramps_size);
+ return ramps_size;
+}
diff --git a/types-ramps.h b/types-ramps.h
new file mode 100644
index 0000000..e8e6956
--- /dev/null
+++ b/types-ramps.h
@@ -0,0 +1,75 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TYPES_RAMPS_H
+#define TYPES_RAMPS_H
+
+#include <libgamma.h>
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Gamma ramps union for all
+ * lbigamma gamma ramps types
+ */
+union gamma_ramps {
+ /**
+ * Ramps with 8-bit value
+ */
+ libgamma_gamma_ramps8_t u8;
+
+ /**
+ * Ramps with 16-bit value
+ */
+ libgamma_gamma_ramps16_t u16;
+
+ /**
+ * Ramps with 32-bit value
+ */
+ libgamma_gamma_ramps32_t u32;
+
+ /**
+ * Ramps with 64-bit value
+ */
+ libgamma_gamma_ramps64_t u64;
+
+ /**
+ * Ramps with `float` value
+ */
+ libgamma_gamma_rampsf_t f;
+
+ /**
+ * Ramps with `double` value
+ */
+ libgamma_gamma_rampsd_t d;
+};
+
+/**
+ * Marshal a ramp trio
+ *
+ * @param this The ramps
+ * @param buf Output buffer for the marshalled ramps,
+ * `NULL` just measure how large the buffers
+ * needs to be
+ * @param ramps_size The byte-size of ramps
+ * @return The number of marshalled byte
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+size_t gamma_ramps_marshal(const union gamma_ramps *restrict this, void *restrict buf, size_t ramps_size);
+
+/**
+ * Unmarshal a ramp trio
+ *
+ * @param this Output for the ramps
+ * @param buf Buffer with the marshalled ramps
+ * @param ramps_size The byte-size of ramps
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t gamma_ramps_unmarshal(union gamma_ramps *restrict this, const void *restrict buf, size_t ramps_size);
+
+#endif
diff --git a/types-ring.c b/types-ring.c
new file mode 100644
index 0000000..6cceea1
--- /dev/null
+++ b/types-ring.c
@@ -0,0 +1,193 @@
+/* See LICENSE file for copyright and license details. */
+#include "types-ring.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+/**
+ * Initialise a ring buffer
+ *
+ * @param this The ring buffer
+ */
+void
+ring_initialise(struct ring *restrict this)
+{
+ this->start = 0;
+ this->end = 0;
+ this->size = 0;
+ this->buffer = NULL;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+/**
+ * Marshal a ring buffer
+ *
+ * @param this The ring buffer
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how large buffer
+ * is needed
+ * @return The number of marshalled bytes
+ */
+size_t
+ring_marshal(const struct ring *restrict this, void *restrict buf)
+{
+ size_t off = 0, n = this->end - this->start;
+ char *bs = buf;
+
+ if (bs)
+ *(size_t *)&bs[off] = n;
+ off += sizeof(size_t);
+
+ if (bs)
+ memcpy(&bs[off], this->buffer + this->start, n);
+ off += n;
+
+ return off;
+}
+
+
+/**
+ * Unmarshal a ring buffer
+ *
+ * @param this Output parameter for the ring buffer
+ * @param buf Buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+size_t
+ring_unmarshal(struct ring *restrict this, const void *restrict buf)
+{
+ size_t off = 0;
+ const char *bs = buf;
+
+ ring_initialise(this);
+
+ this->size = this->end = *(const size_t *)&bs[off];
+ off += sizeof(size_t);
+
+ if (this->end > 0) {
+ this->buffer = malloc(this->end);
+ if (!this->buffer)
+ return 0;
+
+ memcpy(this->buffer, bs + off, this->end);
+ off += this->end;
+ }
+
+ return off;
+}
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+
+/**
+ * Append data to a ring buffer
+ *
+ * @param this The ring buffer
+ * @param data The new data
+ * @param n The number of bytes in `data`
+ * @return Zero on success, -1 on error
+ */
+int
+ring_push(struct ring *restrict this, void *restrict data, size_t n)
+{
+ size_t used = 0;
+ char *restrict new;
+
+ if (this->start == this->end) {
+ if (this->buffer)
+ used = this->size;
+ } else if (this->start > this->end) {
+ used = this->size - this->start + this->end;
+ } else {
+ used = this->start - this->end;
+ }
+
+ if (used + n > this->size) {
+ new = malloc(used + n);
+ if (!new)
+ return -1;
+ if (this->buffer) {
+ if (this->start < this->end) {
+ memcpy(new, this->buffer + this->start, this->end - this->start);
+ } else {
+ memcpy(new, this->buffer + this->start, this->size - this->start);
+ memcpy(new + this->size - this->start, this->buffer, this->end);
+ }
+ }
+ memcpy(new + used, data, n);
+ this->buffer = new;
+ this->start = 0;
+ this->end = used + n;
+ this->size = used + n;
+ } else if (this->start >= this->end || this->end + n <= this->size) {
+ memcpy(&this->buffer[this->end], data, n);
+ this->end += n;
+ } else {
+ memcpy(&this->buffer[this->end], data, this->size - this->end);
+ data = &((char *)data)[this->size - this->end];
+ n -= this->size - this->end;
+ memcpy(this->buffer, data, n);
+ this->end = n;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get queued data from a ring buffer
+ *
+ * It can take up to two calls (with `ring_pop` between)
+ * to get all queued data
+ *
+ * @param this The ring buffer
+ * @param n Output parameter for the length
+ * of the returned segment
+ * @return The beginning of the queued data,
+ * `NULL` if there is nothing more
+ */
+void *
+ring_peek(struct ring *restrict this, size_t *restrict n)
+{
+ if (!this->buffer) {
+ *n = 0;
+ return NULL;
+ }
+
+ if (this->start < this->end)
+ *n = this->end - this->start;
+ else
+ *n = this->size - this->start;
+ return this->buffer + this->start;
+}
+
+
+/**
+ * Dequeue data from a ring bubber
+ *
+ * @param this The ring buffer
+ * @param n The number of bytes to dequeue
+ */
+void
+ring_pop(struct ring *restrict this, size_t n)
+{
+ this->start += n;
+ this->start %= this->size;
+ if (this->start == this->end) {
+ free(this->buffer);
+ this->start = 0;
+ this->end = 0;
+ this->size = 0;
+ this->buffer = NULL;
+ }
+}
diff --git a/types-ring.h b/types-ring.h
new file mode 100644
index 0000000..c8b5f77
--- /dev/null
+++ b/types-ring.h
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TYPES_RING_H
+#define TYPES_RING_H
+
+#include <stdlib.h>
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Ring buffer
+ */
+struct ring {
+ /**
+ * Buffer for the data
+ */
+ char *restrict buffer;
+
+ /**
+ * The first set byte in `.buffer`
+ */
+ size_t start;
+
+ /**
+ * The last set byte in `.buffer`, plus 1
+ */
+ size_t end;
+
+ /**
+ * The size of `.buffer`
+ */
+ size_t size;
+};
+
+/**
+ * Initialise a ring buffer
+ *
+ * @param this The ring buffer
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void ring_initialise(struct ring *restrict this);
+
+/**
+ * Marshal a ring buffer
+ *
+ * @param this The ring buffer
+ * @param buf Output buffer for the marshalled data,
+ * `NULL` to only measure how large buffer
+ * is needed
+ * @return The number of marshalled bytes
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+size_t ring_marshal(const struct ring *restrict this, void *restrict buf);
+
+/**
+ * Unmarshal a ring buffer
+ *
+ * @param this Output parameter for the ring buffer
+ * @param buf Buffer with the marshalled data
+ * @return The number of unmarshalled bytes, 0 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+size_t ring_unmarshal(struct ring *restrict this, const void *restrict buf);
+
+/**
+ * Append data to a ring buffer
+ *
+ * @param this The ring buffer
+ * @param data The new data
+ * @param n The number of bytes in `data`
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__(1))))
+int ring_push(struct ring *restrict this, void *restrict data, size_t n);
+
+/**
+ * Get queued data from a ring buffer
+ *
+ * It can take up to two calls (with `ring_pop` between)
+ * to get all queued data
+ *
+ * @param this The ring buffer
+ * @param n Output parameter for the length
+ * of the returned segment
+ * @return The beginning of the queued data,
+ * `NULL` if there is nothing more
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void *ring_peek(struct ring *restrict this, size_t *restrict n);
+
+/**
+ * Dequeue data from a ring bubber
+ *
+ * @param this The ring buffer
+ * @param n The number of bytes to dequeue
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+void ring_pop(struct ring *restrict this, size_t n);
+
+/**
+ * Release resource allocated to a ring buffer
+ *
+ * @param this The ring buffer
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static inline void
+ring_destroy(struct ring *restrict this)
+{
+ free(this->buffer);
+}
+
+/**
+ * Check whether there is more data waiting
+ * in a ring buffer
+ *
+ * @param this The ring buffer
+ * @return 1 if there is more data, 0 otherwise
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+static inline int
+ring_have_more(struct ring *restrict this)
+{
+ return !!this->buffer;
+}
+
+#endif
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..a6a97a9
--- /dev/null
+++ b/util.c
@@ -0,0 +1,274 @@
+/* See LICENSE file for copyright and license details. */
+#include "util.h"
+
+#include <libclut.h>
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+
+/**
+ * Duplicate a memory segment
+ *
+ * @param src The memory segment, must not be `NULL`
+ * @param n The size of the memory segment, must not be zero
+ * @return The duplicate of the memory segment,
+ * `NULL` on error
+ */
+void *
+memdup(const void *restrict src, size_t n)
+{
+ void *dest = malloc(n);
+ if (!dest)
+ return NULL;
+ memcpy(dest, src, n);
+ return dest;
+}
+
+
+/**
+ * Read an entire file
+ *
+ * Not cancelled by `EINTR`
+ *
+ * @param fd The file descriptor
+ * @param n Output for the size of the file
+ * @return The read content, plus a NUL byte at
+ * the end (not counted in `*n`)
+ */
+void *
+nread(int fd, size_t *restrict n)
+{
+ size_t size = 32;
+ ssize_t got;
+ struct stat st;
+ char *buf, *new;
+
+ *n = 0;
+
+ if (!fstat(fd, &st))
+ size = st.st_size <= 0 ? 32 : (size_t)(st.st_size);
+
+ buf = malloc(size + 1);
+ if (!buf)
+ return NULL;
+
+ for (;;) {
+ if (*n == size) {
+ new = realloc(buf, (size <<= 1) + 1);
+ if (!new)
+ goto fail;
+ buf = new;
+ }
+
+ got = read(fd, buf + *n, size - *n);
+ if (got <= 0) {
+ if (!got)
+ break;
+ if (errno == EINTR)
+ continue;
+ goto fail;
+ }
+ *n += (size_t)got;
+ }
+
+ buf[*n] = '\0';
+ return buf;
+
+fail:
+ free(buf);
+ return NULL;
+}
+
+
+/**
+ * Write an entire buffer to a file
+ *
+ * Not cancelled by `EINTR`
+ *
+ * @param fd The file descriptor
+ * @param buf The buffer which shall be written to the fail
+ * @param n The size of the buffer
+ * @return The number of written bytes, less than `n`
+ * on error, cannot exceed `n`
+ */
+size_t
+nwrite(int fd, const void *restrict buf, size_t n)
+{
+ const char *restrict bs = buf;
+ ssize_t wrote;
+ size_t ptr = 0;
+
+ while (ptr < n) {
+ wrote = write(fd, bs + ptr, n - ptr);
+ if (wrote <= 0) {
+ if (wrote < 0 && errno == EINTR)
+ continue;
+ return ptr;
+ }
+ ptr += (size_t)wrote;
+ }
+
+ return ptr;
+}
+
+
+/**
+ * Perform a timed suspention of the process.
+ * The process resumes when the timer expires,
+ * or when it is interrupted.
+ *
+ * @param ms The number of milliseconds to sleep,
+ * must be less than 1000
+ */
+void
+msleep(unsigned ms)
+{
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = (long)ms * 1000000L;
+ if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL) == ENOTSUP)
+ nanosleep(&ts, NULL);
+}
+
+
+/**
+ * Check whether a NUL-terminated string is encoded in UTF-8
+ *
+ * @param string The string
+ * @return Zero if good, -1 on encoding error
+ */
+int
+verify_utf8(const char *restrict string)
+{
+ static long BYTES_TO_MIN_BITS[] = {0, 0, 8, 12, 17, 22, 37};
+ static long BYTES_TO_MAX_BITS[] = {0, 7, 11, 16, 21, 26, 31};
+ long int bytes = 0, read_bytes = 0, bits = 0, c, character = 0;
+
+ /* min bits max bits
+ 0....... 0 7
+ 110..... 10...... 8 11
+ 1110.... 10...... 10...... 12 16
+ 11110... 10...... 10...... 10...... 17 21
+ 111110.. 10...... 10...... 10...... 10...... 22 26
+ 1111110. 10...... 10...... 10...... 10...... 10...... 27 31
+ */
+
+ while ((c = (long)(*string++))) {
+ if (!read_bytes) {
+ /* First byte of the character. */
+
+ if ((c & 0x80) == 0x00) {
+ /* Single-byte character. */
+ continue;
+ }
+
+ if ((c & 0xC0) == 0x80) {
+ /* Single-byte character marked as multibyte, or
+ a non-first byte in a multibyte character. */
+ return -1;
+ }
+
+ /* Multibyte character. */
+ while ((c & 0x80)) {
+ bytes++;
+ c <<= 1;
+ }
+ read_bytes = 1;
+ character = c & 0x7F;
+ if (bytes > 6) {
+ /* 31-bit characters can be encoded with 6-bytes,
+ and UTF-8 does not cover higher code points. */
+ return -1;
+ }
+ } else {
+ /* Not first byte of the character. */
+
+ if ((c & 0xC0) != 0x80) {
+ /* Beginning of new character before a
+ multibyte character has ended. */
+ return -1;
+ }
+
+ character = (character << 6) | (c & 0x7F);
+
+ if (++read_bytes < bytes) {
+ /* Not at last byte yet. */
+ continue;
+ }
+
+ /* Check that the character is not unnecessarily long. */
+ while (character) {
+ character >>= 1, bits++;
+ }
+ if ((bits < BYTES_TO_MIN_BITS[bytes]) || (BYTES_TO_MAX_BITS[bytes] < bits)) {
+ return -1;
+ }
+
+ read_bytes = bytes = bits = 0;
+ }
+ }
+
+ /* Make sure we did not stop at the middle of a multibyte character. */
+ return !read_bytes ? 0 : -1;
+}
+
+
+/**
+ * Make identity mapping ramps
+ *
+ * @param ramps Output parameter for the ramps
+ * @param output The output for which the ramps shall be configured
+ * @return Zero on success, -1 on error
+ */
+int
+make_plain_ramps(union gamma_ramps *restrict ramps, struct output *restrict output)
+{
+ COPY_RAMP_SIZES(&ramps->u8, output);
+ switch (output->depth) {
+ case 8:
+ if (libgamma_gamma_ramps8_initialise(&(ramps->u8)))
+ return -1;
+ libclut_start_over(&ramps->u8, UINT8_MAX, uint8_t, 1, 1, 1);
+ break;
+
+ case 16:
+ if (libgamma_gamma_ramps16_initialise(&(ramps->u16)))
+ return -1;
+ libclut_start_over(&ramps->u16, UINT16_MAX, uint16_t, 1, 1, 1);
+ break;
+
+ case 32:
+ if (libgamma_gamma_ramps32_initialise(&(ramps->u32)))
+ return -1;
+ libclut_start_over(&ramps->u32, UINT32_MAX, uint32_t, 1, 1, 1);
+ break;
+
+ case 64:
+ if (libgamma_gamma_ramps64_initialise(&(ramps->u64)))
+ return -1;
+ libclut_start_over(&ramps->u64, UINT64_MAX, uint64_t, 1, 1, 1);
+ break;
+
+ case -1:
+ if (libgamma_gamma_rampsf_initialise(&(ramps->f)))
+ return -1;
+ libclut_start_over(&ramps->f, 1.0f, float, 1, 1, 1);
+ break;
+
+ case -2:
+ if (libgamma_gamma_rampsd_initialise(&(ramps->d)))
+ return -1;
+ libclut_start_over(&ramps->d, (double)1, double, 1, 1, 1);
+ break;
+
+ default:
+ abort();
+ }
+ return 0;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..0901449
--- /dev/null
+++ b/util.h
@@ -0,0 +1,81 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef UTIL_H
+#define UTIL_H
+
+#include "types-output.h"
+
+#ifndef GCC_ONLY
+# if defined(__GNUC__) && !defined(__clang__)
+# define GCC_ONLY(...) __VA_ARGS__
+# else
+# define GCC_ONLY(...) /* nothing */
+# endif
+#endif
+
+/**
+ * Duplicate a memory segment
+ *
+ * @param src The memory segment, must not be `NULL`
+ * @param n The size of the memory segment, must not be zero
+ * @return The duplicate of the memory segment,
+ * `NULL` on error
+ */
+GCC_ONLY(__attribute__((__malloc__, __nonnull__)))
+void *memdup(const void *restrict src, size_t n);
+
+/**
+ * Read an entire file
+ *
+ * Not cancelled by `EINTR`
+ *
+ * @param fd The file descriptor
+ * @param n Output for the size of the file
+ * @return The read content, plus a NUL byte at
+ * the end (not counted in `*n`)
+ */
+GCC_ONLY(__attribute__((__malloc__)))
+void *nread(int fd, size_t *restrict n);
+
+/**
+ * Write an entire buffer to a file
+ *
+ * Not cancelled by `EINTR`
+ *
+ * @param fd The file descriptor
+ * @param buf The buffer which shall be written to the fail
+ * @param n The size of the buffer
+ * @return The number of written bytes, less than `n`
+ * on error, cannot exceed `n`
+ */
+size_t nwrite(int fd, const void *restrict buf, size_t n);
+
+/**
+ * Perform a timed suspention of the process.
+ * The process resumes when the timer expires,
+ * or when it is interrupted.
+ *
+ * @param ms The number of milliseconds to sleep,
+ * must be less than 1000
+ */
+void msleep(unsigned ms);
+
+/**
+ * Check whether a NUL-terminated string is encoded in UTF-8
+ *
+ * @param string The string
+ * @return Zero if good, -1 on encoding error
+ */
+GCC_ONLY(__attribute__((__pure__, __nonnull__)))
+int verify_utf8(const char *restrict string);
+
+/**
+ * Make identity mapping ramps
+ *
+ * @param ramps Output parameter for the ramps
+ * @param output The output for which the ramps shall be configured
+ * @return Zero on success, -1 on error
+ */
+GCC_ONLY(__attribute__((__nonnull__)))
+int make_plain_ramps(union gamma_ramps *restrict ramps, struct output *restrict output);
+
+#endif