aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSpacedio <spacedio@thernusen.net>2026-02-26 19:20:14 -0500
committerSpacedio <spacedio@thernusen.net>2026-02-26 19:20:14 -0500
commitdb657412e6ae93341ed42d59df6aef564a739a1f (patch)
tree6f67f55f6dc61fb0afa8829a950f0dbf1a0aeadf
downloadlightdm-mini-greeter-master.tar.gz
Initial commit after cloneHEADmaster
-rw-r--r--CHANGELOG.md112
-rw-r--r--LICENSE674
-rw-r--r--Makefile.am60
-rw-r--r--README.md235
-rwxr-xr-xautogen.sh2
-rw-r--r--configure.ac31
-rw-r--r--data/lightdm-mini-greeter.conf114
-rw-r--r--data/lightdm-mini-greeter.desktop5
-rw-r--r--debian/changelog41
-rw-r--r--debian/compat1
-rw-r--r--debian/control19
-rw-r--r--debian/copyright8
-rw-r--r--debian/postinst12
-rw-r--r--debian/prerm11
-rwxr-xr-xdebian/rules12
-rw-r--r--debian/source/format1
-rw-r--r--screenshot.pngbin0 -> 7117 bytes
-rw-r--r--src/app.c60
-rw-r--r--src/app.h36
-rw-r--r--src/callbacks.c152
-rw-r--r--src/callbacks.h15
-rw-r--r--src/compat.c25
-rw-r--r--src/compat.h12
-rw-r--r--src/config.c435
-rw-r--r--src/config.h68
-rw-r--r--src/focus_ring.c105
-rw-r--r--src/focus_ring.h31
-rw-r--r--src/main.c28
-rw-r--r--src/ui.c430
-rw-r--r--src/ui.h24
-rw-r--r--src/utils.c70
-rw-r--r--src/utils.h14
32 files changed, 2843 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..37b8c05
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,112 @@
+# CHANGELOG
+
+## 0.6.0
+
+* Add a `show-sys-info` configuration option to show the username, hostname, &
+ current time above the password label/input. Additional configuration options
+ let you customize the font, size, color, & spacing. The output format is
+ currently fixed to `<user>@<hostname>` & `HH:MM` but may be customizable in
+ the future.
+* Add the ability to cycle between available sessions instead of only using
+ LightDM's `default-session`. Change sessions by pressing `<mod-key>+e`; a
+ `session-key` configuration option has been added to allow for customization.
+* Make every configuration option besides `greeter.user` optional. Any
+ undefined configuration options will fallback to their original values.
+* Add a `background-image-size` configuration option for scaling the background
+ image to fit or fill the screen.
+* Added a `y-pos` and `x-pos` configuration option to position the login window
+ relative to the screen.
+
+## v0.5.1
+
+* Fix crash when `password-alignment` option is missing from configuration
+ file.
+
+## v0.5.0
+
+* Allow setting the `password-alignment` option to `center`, in addition to
+ `left` or `right`.
+* Add a `password-border-radius` configuration option to set the border radius
+ of the password input.
+* Add a `password-character` configuration option to set the character used to
+ mask the entered password. `-1` specifies the default character, `0` hides
+ any entered text, & the first character is used for any other value.
+* Document how to make configuration file a symlink into home directories.
+* Improve error message when configuration file is unreadable.
+* Disable the password input while authenticating. This fixes a bug causing
+ lightdm to crash if the greeter tried authenticating before a previous
+ authentication attempt had completed.
+* Add a `password-input-width` configuration option to set the number of
+ characters that should fit in the password input. This is just a suggested
+ width - GTK may render a narrower input.
+* Ensure the mouse cursor is always hidden.
+* Add a `show-image-on-all-monitors` configuration option to display the
+ background image on every monitor instead of just the primary monitor.
+* Ignore any trailing whitespace in the `user`, `mod-key`, &
+ `password-alignment` configuration options.
+
+## v0.4.0
+
+* Add `password-border-color` & `password-border-width` options for customizing
+ the password input's border independently of the main window's border.
+* Fix default path to config file if not set by autotools.
+* Add `font-weight` & `font-style` options to customize the bolding &
+ italicizing of all text.
+* Use the alternatives system for Debian-based builds.
+* Add a `password-alignment` option to customize the alignment of the text in the
+ password input.
+* Add an `invalid-password-text` option to customize the wrong password error
+ text.
+
+## v0.3.4
+
+* Fix flashing of password input on start.
+* Fix background color on primary monitor when background image is not set.
+
+## v0.3.3
+
+* Fix background image aligment in multi-head configurations.
+* Fix builds with liblightdm v1.19.1 and below.
+* Fix customization of label colors.
+
+## v0.3.2
+
+* Fix linker flag build failures.
+* Fix silent failures when using old configuration files that are missing newly
+ added options.
+* Fix positioning main window in the center of the primary monitor for various
+ multi-head configurations.
+
+## v0.3.1
+
+* Fix white background when the `background-image` option is blank or not
+ present in the config file.
+
+## v0.3.0
+
+* Add a `password-label-text` option to customize the label text.
+* Add a `show-input-cursor` option to hide the input's cursor.
+* Add a `background-image` option to set a centered background image.
+* Change the default value of the `user` option to `CHANGE_ME`.
+
+## v0.2.0
+
+* Add a `font-size` option to the configuration file.
+* Fix start failure when no `user-session` is specified.
+* Fix color of password input's cursor.
+
+## v0.1.3
+
+* Fix a build issue with some compilers.
+
+## v0.1.2
+
+* Fix background color when using multiple monitors.
+
+## v0.1.1
+
+* Fix various warnings caused by the generated CSS.
+
+## v0.1.0
+
+* Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ 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.
+
+ Preamble
+
+ 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>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..dff5d17
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,60 @@
+# Global Options
+AUTOMAKE_OPTIONS = \
+ foreign # Disable requirement for README, AUTHORS, COPYING
+
+AM_CFLAGS = -g -Wall -O3 --std=c11 -Wextra -Werror --std=c11 -pedantic \
+ -Wno-unused-parameter -Wfloat-equal -Wundef -Wshadow \
+ -Wpointer-arith -Wcast-align -Wstrict-prototypes \
+ -Wstrict-overflow=5 -Wwrite-strings -Waggregate-return \
+ -Wswitch-enum -Wconversion -Wunreachable-code -Wformat=2 \
+ -Winit-self \
+ -ftrapv -fverbose-asm \
+ -DCONFIG_FILE=\""$(sysconfdir)/lightdm/lightdm-mini-greeter.conf"\"
+
+
+# Packaging
+EXTRA_DIST = \
+ autogen.sh
+
+DISTCLEANFILES = \
+ aclocal.m4
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ compile \
+ defines.h.in \
+ configure \
+ depcomp \
+ install-sh \
+ missing
+
+
+# Data Files
+xgreetersdir = $(datadir)/xgreeters
+dist_xgreeters_DATA = data/lightdm-mini-greeter.desktop
+
+configdir = $(sysconfdir)/lightdm
+dist_config_DATA = data/lightdm-mini-greeter.conf
+
+
+# Greeter Executable
+greeterdir = $(bindir)
+greeter_PROGRAMS = lightdm-mini-greeter
+
+lightdm_mini_greeter_SOURCES = \
+ src/main.c \
+ src/app.c \
+ src/callbacks.c \
+ src/compat.c \
+ src/config.c \
+ src/focus_ring.c \
+ src/ui.c \
+ src/utils.c
+
+lightdm_mini_greeter_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(GTK_CFLAGS) \
+ $(LIGHTDM_CFLAGS)
+lightdm_mini_greeter_LDADD = \
+ $(GTK_LIBS) \
+ $(LIGHTDM_LIBS)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2a1a183
--- /dev/null
+++ b/README.md
@@ -0,0 +1,235 @@
+# Mini-Greeter
+
+[![AUR package](https://repology.org/badge/version-for-repo/aur/lightdm-mini-greeter.svg)](https://aur.archlinux.org/packages/lightdm-mini-greeter) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a1c58074072542be8ea60d1bf14863fc)](https://www.codacy.com/gh/prikhi/lightdm-mini-greeter/dashboard)
+
+A minimal but highly configurable single-user GTK3 greeter for LightDM.
+
+Inspired by the SLiM Display Manager & LightDM GTK3 Greeter.
+
+
+## Goals
+
+Eventually this is will present a more customizable interface:
+
+* Randomized Background Wallpapers
+* Configurable language/session info? (lightdm provides this already?)
+* Handle GdkDisplay's `monitor-added` & `monitor-removed` signals
+
+[Open Feature Requests](http://bugs.sleepanarchy.com/projects/mini-greeter/issues/)
+
+
+## Current Status
+
+Right now you can:
+
+* log in
+* hide the `Password:` label & customize the text
+* hide the password input's cursor
+* show the user, hostname, & current time
+* set the password masking character
+* set the size of the login window, the font, & every color
+* set & scale a background image
+* use modifiable hotkeys to cycle through sessions or trigger a shutdown,
+ restart, hibernate, or suspend
+* reposition window on the x or y axis
+
+![A screen with a dark background and a single password input box in the center](https://raw.githubusercontent.com/prikhi/lightdm-mini-greeter/refs/heads/master/screenshot.png "Mini Greeter Screenshot")
+
+
+## Install
+
+### Arch Linux
+
+Install the [lightdm-mini-greeter package][aur-package] from the Arch User
+Repository:
+
+```sh
+yay -S lightdm-mini-greeter
+```
+
+### Gentoo Linux
+
+Emerge the [lightdm-mini-greeter package][gentoo-package]:
+
+```sh
+emerge x11-misc/lightdm-mini-greeter
+```
+
+### NixOS
+
+Enable & configure the greeter & default session in your `configuration.nix`:
+
+```nix
+{
+ services.xserver = {
+ enable = true;
+ displayManager.lightdm.greeters.mini = {
+ enable = true;
+ user = "your-username";
+ extraConfig = ''
+ [greeter]
+ show-password-label = false
+ [greeter-theme]
+ background-image = ""
+ '';
+ };
+ # Optionally, set a default session
+ windowManager = {
+ default = "awesome";
+ awesome.enable = true;
+ };
+ };
+}
+```
+
+Then rebuild & switch your configuration with `nixos-rebuild switch`.
+
+### Debian
+
+Debian packages for the latest `stable` branch are available on the
+[Releases][releases] page.
+
+You can use `debhelper` to build the package yourself:
+
+```sh
+sudo apt-get install build-essential automake pkg-config fakeroot debhelper \
+ liblightdm-gobject-dev libgtk-3-dev
+cd lightdm-mini-greeter
+fakeroot dh binary
+sudo dpkg -i ../lightdm-mini-greeter_*.deb
+```
+
+Note: on Ubuntu, you need `liblightdm-gobject-1-dev` instead of
+`liblightdm-gobject-dev`.
+
+### Manual
+
+You will need `automake`, `pkg-config`, `gtk+`, & `liblightdm-gobject` to build
+the project.
+
+Grab the source, build the greeter, & install it manually:
+
+```sh
+./autogen.sh
+./configure --datadir /usr/share --bindir /usr/bin --sysconfdir /etc
+make
+sudo make install
+```
+
+Run `sudo make uninstall` to remove the greeter.
+
+
+## Configure
+
+Once installed, you should specify `lightdm-mini-greeter` as your
+`greeter-session` in `/etc/lightdm/lightdm.conf`. If you have multiple Desktop
+Environments or Window Managers installed, you can specify the default
+selection by changing the `user-session` option as well(look in
+`/usr/share/xsessions` for possible values).
+
+Modify `/etc/lightdm/lightdm-mini-greeter.conf` to customize the greeter. At
+the very least, you will need to set the `user`. All other settings are
+optional & can be commented out or removed.
+
+You can test it out using LightDM's `test-mode`:
+
+ lightdm --test-mode -d
+
+Or with `dm-tool`:
+
+ dm-tool add-nested-seat
+
+Note: If you've added a `background-image` it will appear in this preview, but
+it may not appear during normal use if the file is not in directory which
+lightdm has permission to read(like `/etc/lightdm/`). A symlink into this
+location won't work.
+
+### Keyboard layout
+
+If your keyboard layout is loaded from your shell configuration files (`.bashrc`
+for example) then it might not be possible to type certain characters after
+installing lightdm-mini-greeter. You should consider modifying your
+[Xorg keyboard configuration](https://wiki.archlinux.org/index.php/Xorg/Keyboard_configuration#Using_X_configuration_files).
+
+For example for a french keyboard layout (azerty) you should edit/create
+`/etc/X11/xorg.conf.d/00-keyboard.conf` with at least the following options:
+
+```
+Section "InputClass"
+ Identifier "system-keyboard"
+ MatchIsKeyboard "on"
+ Option "XkbModel" "pc104"
+ Option "XkbLayout" "fr"
+EndSection
+```
+
+### Config file in $HOME
+
+You may wish to include your config file in their your home folder/dotfiles so
+it is version controlled & easily transferable between systems. This is
+possible, but on most systems, LightDM will not be able to read the
+configuration file due to permission errors.
+
+The proper way to handle this is to loosen the permissions on your home
+directory a bit.
+
+Start off by adding the `lightdm` user to your user's group:
+
+ sudo usermod -aG $(whoami) lightdm
+
+Allow your user group to read your home directory:
+
+ chmod g+rx ~
+
+Move the mini-greeter config file:
+
+ sudo mv /etc/lightdm/lightdm-mini-greeter.conf ~/.dotfiles/mini-greeter.conf
+
+And then add a symlink pointing to the file in your home directory:
+
+ sudo ln -s ~/.dotfiles/mini-greeter.conf /etc/lightdm/lightdm-mini-greeter.conf
+
+And finally log out & restart LightDM:
+
+ sudo systemctl restart lightdm
+
+If LightDM fails to start back up, check the greeter's log file(usually at
+`/var/log/lightdm/seat0-greeter.log`) for the following line:
+
+ Could not load configuration file: Permission denied
+
+If present, your permissions need further adjustment. You can test your
+permissions by attempting to read the file with `sudo`:
+
+ sudo -u lightdm cat ~/.dotfiles/mini-greeter.conf
+
+
+## Contribute
+
+You can submit feature requests, bug reports, pull requests or patches on
+either [github](http://github.com/prikhi/lightdm-mini-greeter) or
+[redmine](http://bugs.sleepanarchy.com/projects/mini-greeter/).
+
+If you like Mini-Greeter, please consider packaging it for your distribution.
+
+
+### Style
+
+* Use indentation and braces, 4 spaces - no tabs, no trailing whitespace.
+* Declare pointers like this: `char *p1, *p2;`, avoid: `char* p1;`.
+* Function braces should be on their own line.
+* If/else/while/do should always use braces and indentation.
+* Use `g_critical` for irrecoverable user errors, `g_error` for programming
+ errors.
+
+When in doubt, check surrounding code.
+
+
+## License
+
+GPL-3
+
+
+[aur-package]: https://aur.archlinux.org/packages/lightdm-mini-greeter/
+[gentoo-package]: https://packages.gentoo.org/packages/x11-misc/lightdm-mini-greeter
+[releases]: https://github.com/prikhi/lightdm-mini-greeter/releases
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..68f4a17
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+autoreconf -i
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..a71ba3a
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,31 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.69])
+AC_INIT([lightdm-mini-greeter], [0.6.0], [http://github.com/prikhi])
+AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_HEADERS([defines.h])
+
+# Checks for programs.
+AC_PROG_CC
+AM_INIT_AUTOMAKE([subdir-objects])
+
+# Checks for libraries.
+PKG_CHECK_MODULES(GTK, gtk+-3.0 >= 3.14)
+PKG_CHECK_MODULES(LIGHTDM, liblightdm-gobject-1 >= 1.12)
+
+# Checks for header files.
+AC_CHECK_HEADERS([stdlib.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+
+# Checks for library functions.
+
+# Check if liblightdm is v1.19.1 or below for debian builds
+LDM_CHECK=$(pkg-config --max-version=1.19.1 liblightdm-gobject-1)
+AS_IF([test $? = 0],
+ [AC_DEFINE([LIGHTDM_1_19_1_LOWER], [], [Defined if liblightdm is version 1.19.1 or lower])]
+ )
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/data/lightdm-mini-greeter.conf b/data/lightdm-mini-greeter.conf
new file mode 100644
index 0000000..51a0819
--- /dev/null
+++ b/data/lightdm-mini-greeter.conf
@@ -0,0 +1,114 @@
+# LightDM Mini Greeter Configuration
+#
+# To test your configuration out, run: lightdm --test-mode
+
+[greeter]
+# The user to login as.
+user = CHANGE_ME
+# Whether to show the password input's label.
+show-password-label = true
+# The text of the password input's label.
+password-label-text = Password:
+# The text shown when an invalid password is entered. May be blank.
+invalid-password-text = Invalid Password
+# Show a blinking cursor in the password input.
+show-input-cursor = true
+# The text alignment for the password input. Possible values are:
+# "left", "center", or "right"
+password-alignment = right
+# The number of characters that should fit into the password input.
+# A value of -1 will use GTK's default width.
+# Note: The entered width is a suggestion, GTK may render a narrower input.
+password-input-width = -1
+# Show the background image on all monitors or just the primary monitor.
+show-image-on-all-monitors = false
+# Show system info above the password input.
+# `<user>@<hostname>` is shown on the left side, & current time on the right.
+show-sys-info = false
+
+
+[greeter-hotkeys]
+# The modifier key used to trigger hotkeys. Possible values are:
+# "alt", "control" or "meta"
+# meta is also known as the "Windows"/"Super" key
+mod-key = meta
+# Power management shortcuts (single-key, case-sensitive)
+shutdown-key = s
+restart-key = r
+hibernate-key = h
+suspend-key = u
+# Cycle through available sessions
+session-key = e
+
+
+[greeter-theme]
+# A color from X11's `rgb.txt` file, a quoted hex string(`"#rrggbb"`) or a
+# RGB color(`rgb(r,g,b)`) are all acceptable formats.
+
+# The font to use for all text
+font = "Sans"
+# The font size to use for all text
+font-size = 1em
+# The font weight to use for all text
+font-weight = bold
+# The font style to use for all text
+font-style = normal
+# The default text color
+text-color = "#080800"
+# The color of the error text
+error-color = "#F8F8F0"
+# An absolute path to an optional background image.
+# Note: The file should be somewhere that LightDM has permissions to read
+# (e.g., /etc/lightdm/).
+background-image = ""
+# Background image size:
+# auto: unscaled
+# cover: scale image to fill screen space
+# contain: scale image to fit inside screen space
+# (more options: https://www.w3.org/TR/css-backgrounds-3/#background-size)
+background-image-size = auto
+# The screen's background color.
+background-color = "#1B1D1E"
+# The password window's background color
+window-color = "#F92672"
+# The color of the password window's border
+border-color = "#080800"
+# The width of the password window's border.
+# A trailing `px` is required.
+border-width = 2px
+# The pixels of empty space around the password input.
+# Do not include a trailing `px`.
+layout-space = 15
+# The character used to mask your password. Possible values are:
+# "-1", "0", or a single unicode character(including emojis)
+# A value of -1 uses the default bullet & 0 displays no characters when you
+# type your password.
+password-character = -1
+# The color of the text in the password input.
+password-color = "#F8F8F0"
+# The background color of the password input.
+password-background-color = "#1B1D1E"
+# The color of the password input's border.
+# Falls back to `border-color` if missing.
+password-border-color = "#080800"
+# The width of the password input's border.
+# Falls back to `border-width` if missing.
+password-border-width = 2px
+# The border radius of the password input.
+password-border-radius = 0.341125em
+# Override font for system info
+# Falls back to `font` if missing.
+sys-info-font = "Mono"
+# Set font size of system info
+# Falls back to `font-size` if missing.
+sys-info-font-size = 0.8em
+# Override color for system info text
+# Falls back to `text-color` if missing.
+#sys-info-color = "#080800"
+# Margins around the system info section
+# The default `-5px -5px -5px` works well with the password label enabled.
+# If you have the label disabled, you might want to try `-5px -5px 0px`
+sys-info-margin = -5px -5px -5px
+# Relative position of window on screen (0-1 float)
+x-pos = 0.5
+y-pos = 0.5 \ No newline at end of file
diff --git a/data/lightdm-mini-greeter.desktop b/data/lightdm-mini-greeter.desktop
new file mode 100644
index 0000000..4ce8f32
--- /dev/null
+++ b/data/lightdm-mini-greeter.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=LightDM Mini Greeter
+Comment=A minimal, highly configurable GTK3 greeter
+Exec=lightdm-mini-greeter
+Type=Application
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..d635d1b
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,41 @@
+lightdm-mini-greeter (0.5.1-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Mon, 01 Mar 2021 19:22:00 -0500
+
+lightdm-mini-greeter (0.5.0-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Tue, 23 Feb 2021 23:29:15 -0500
+
+lightdm-mini-greeter (0.4.0-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Fri, 03 Apr 2020 06:50:10 -0400
+
+lightdm-mini-greeter (0.3.4-1.1) unstable; urgency=medium
+
+ * Register lightdm-mini-greeter in debian alternatives system.
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Thu, 02 May 2019 07:50:10 +0300
+
+lightdm-mini-greeter (0.3.4-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Thu, 02 Nov 2018 10:01:00 -0400
+
+lightdm-mini-greeter (0.3.3-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Thu, 01 Nov 2018 18:55:01 -0400
+
+lightdm-mini-greeter (0.3.2-1) unstable; urgency=medium
+
+ * Initial package
+
+ -- Pavan Rikhi <pavan.rikhi@gmail.com> Sun, 14 Oct 2018 10:32:01 -0400
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..4266e7b
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,19 @@
+Source: lightdm-mini-greeter
+Section: x11
+Priority: optional
+Maintainer: Pavan Rikhi <pavan.rikhi@gmail.com>
+Build-Depends: debhelper (>= 9),
+ pkg-config,
+ libgtk-3-dev,
+ liblightdm-gobject-dev
+Standards-Version: 3.9.8
+Homepage: https://github.com/prikhi/lightdm-mini-greeter
+Vcs-Git: https://github.com/prikhi/lightdm-mini-greeter.git
+Vcs-Browser: https://github.com/prikhi/lightdm-mini-greeter
+
+Package: lightdm-mini-greeter
+Provides: lightdm-greeter
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: a minimal LightDM greeter
+ A minimal but highly configurable single-user GTK3 greeter for LightDM
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..67aec79
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,8 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: LightDM Mini Greeter
+Upstream-Contact: Pavan Rikhi <pavan.rikhi@gmail.com>
+Source: https://github.com/prikhi/lightdm-mini-greeter
+
+Files: *
+Copyright: Pavan Rikhi
+License: GPL-3+
diff --git a/debian/postinst b/debian/postinst
new file mode 100644
index 0000000..457a62a
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "configure" ]; then
+ update-alternatives --install /usr/share/xgreeters/lightdm-greeter.desktop \
+ lightdm-greeter /usr/share/xgreeters/lightdm-mini-greeter.desktop 60
+fi
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/prerm b/debian/prerm
new file mode 100644
index 0000000..72c87a9
--- /dev/null
+++ b/debian/prerm
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+if [ "$1" = "remove" ]; then
+ update-alternatives --remove lightdm-greeter /usr/share/xgreeters/lightdm-mini-greeter.desktop
+fi
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..1034960
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,12 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@
+
+override_dh_auto_configure:
+ ./autogen.sh
+ dh_auto_configure
+
+override_dh_auto_clean:
+ make maintainer-clean
+ dh_auto_clean
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 0000000..1558261
--- /dev/null
+++ b/screenshot.png
Binary files differ
diff --git a/src/app.c b/src/app.c
new file mode 100644
index 0000000..0877b55
--- /dev/null
+++ b/src/app.c
@@ -0,0 +1,60 @@
+/* Application Initialization Things */
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <lightdm.h>
+
+#include "app.h"
+#include "callbacks.h"
+#include "config.h"
+
+
+/* Initialize the Greeter & UI */
+App *initialize_app(int argc, char **argv)
+{
+ g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL);
+ gtk_init(&argc, &argv);
+
+ // Allocate & Initialize
+ App *app = malloc(sizeof(App));
+ if (app == NULL) {
+ g_error("Could not allocate memory for App");
+ }
+
+ app->config = initialize_config();
+ app->greeter = lightdm_greeter_new();
+ app->ui = initialize_ui(app->config);
+
+ // Connect Greeter & UI Signals
+ g_signal_connect(app->greeter, "authentication-complete",
+ G_CALLBACK(authentication_complete_cb), app);
+ app->password_callback_id =
+ g_signal_connect(GTK_ENTRY(APP_PASSWORD_INPUT(app)), "activate",
+ G_CALLBACK(handle_password), app);
+ // This was added to fix a bug where the background window would be focused
+ // instead of the main window, preventing users from entering their password.
+ // It's undocument & probably not necessary any more. Investigate & remove.
+ for (int m = 0; m < APP_MONITOR_COUNT(app); m++) {
+ g_signal_connect(GTK_WIDGET(APP_BACKGROUND_WINDOWS(app)[m]),
+ "key-press-event",
+ G_CALLBACK(handle_tab_key), app);
+ }
+ g_signal_connect(GTK_WIDGET(APP_MAIN_WINDOW(app)), "key-press-event",
+ G_CALLBACK(handle_hotkeys), app);
+ // Update the current time every 15 seconds
+ if (app->config->show_sys_info) {
+ handle_time_update(app);
+ g_timeout_add_seconds(15, G_SOURCE_FUNC(handle_time_update), app);
+ }
+
+ return app;
+}
+
+
+/* Free any dynamically allocated memory */
+void destroy_app(App *app)
+{
+ destroy_config(app->config);
+ free(app->ui);
+ free(app);
+}
diff --git a/src/app.h b/src/app.h
new file mode 100644
index 0000000..4bfdab8
--- /dev/null
+++ b/src/app.h
@@ -0,0 +1,36 @@
+#ifndef APP_H
+#define APP_H
+
+#include <lightdm.h>
+
+#include "config.h"
+#include "focus_ring.h"
+#include "ui.h"
+
+
+typedef struct App_ {
+ Config *config;
+ LightDMGreeter *greeter;
+ UI *ui;
+ FocusRing *session_ring;
+
+ // Signal Handler ID for the `handle_password` callback
+ gulong password_callback_id;
+} App;
+
+
+App *initialize_app(int argc, char **argv);
+void destroy_app(App *app);
+
+/* Config Member Accessors */
+#define APP_LOGIN_USER(app) (app)->config->login_user
+
+/* UI Member Accessors */
+#define APP_BACKGROUND_WINDOWS(app) (app)->ui->background_windows
+#define APP_MONITOR_COUNT(app) (app)->ui->monitor_count
+#define APP_MAIN_WINDOW(app) (app)->ui->main_window
+#define APP_PASSWORD_INPUT(app) (app)->ui->password_input
+#define APP_FEEDBACK_LABEL(app) (app)->ui->feedback_label
+#define APP_TIME_LABEL(app) (app)->ui->time_label
+
+#endif
diff --git a/src/callbacks.c b/src/callbacks.c
new file mode 100644
index 0000000..7cefa42
--- /dev/null
+++ b/src/callbacks.c
@@ -0,0 +1,152 @@
+/* Callback Functions for LightDM & GTK */
+#include <gtk/gtk.h>
+#include <lightdm.h>
+#include <string.h>
+#include <time.h>
+
+#include "app.h"
+#include "utils.h"
+#include "focus_ring.h"
+#include "callbacks.h"
+#include "compat.h"
+
+static void set_ui_feedback_label(App *app, gchar *feedback_text);
+
+
+/* LightDM Callbacks */
+
+/* Start the Selected Session Once Fully Authenticated.
+ *
+ * The callback will clear & re-enable the input widget, and re-add the
+ * `handle_password` callback so the user can try again if authentication
+ * fails.
+ */
+void authentication_complete_cb(LightDMGreeter *greeter, App *app)
+{
+ if (lightdm_greeter_get_is_authenticated(greeter)) {
+ const gchar *session = focus_ring_get_value(app->session_ring);
+
+ g_message("Attempting to start session: %s", session);
+
+ gboolean session_started_successfully =
+ !lightdm_greeter_start_session_sync(greeter, session, NULL);
+
+ if (!session_started_successfully) {
+ g_message("Unable to start session");
+ }
+ } else {
+ g_message("Authentication failed");
+ if (strlen(app->config->invalid_password_text) > 0) {
+ set_ui_feedback_label(app, app->config->invalid_password_text);
+ }
+ begin_authentication_as_default_user(app);
+ }
+ gtk_entry_set_text(GTK_ENTRY(APP_PASSWORD_INPUT(app)), "");
+ gtk_editable_set_editable(GTK_EDITABLE(APP_PASSWORD_INPUT(app)), TRUE);
+ app->password_callback_id =
+ g_signal_connect(GTK_ENTRY(APP_PASSWORD_INPUT(app)), "activate",
+ G_CALLBACK(handle_password), app);
+}
+
+
+
+/* GUI Callbacks */
+
+/* Attempt to Authenticate When a Password is Entered.
+ *
+ * The callback disables itself & the input widget to prevent two
+ * authentication attempts from running at the same time - which would cause
+ * LightDM to throw a critical error.
+ */
+void handle_password(GtkWidget *password_input, App *app)
+{
+ if (app->password_callback_id != 0) {
+ g_signal_handler_disconnect(GTK_ENTRY(APP_PASSWORD_INPUT(app)),
+ app->password_callback_id);
+ app->password_callback_id = 0;
+ }
+
+ if (!lightdm_greeter_get_is_authenticated(app->greeter)) {
+ gtk_editable_set_editable(GTK_EDITABLE(password_input), FALSE);
+ if (!lightdm_greeter_get_in_authentication(app->greeter)) {
+ begin_authentication_as_default_user(app);
+ }
+ g_message("Using entered password to authenticate");
+ const gchar *password_text =
+ gtk_entry_get_text(GTK_ENTRY(password_input));
+ compat_greeter_respond(app->greeter, password_text, NULL);
+ } else {
+ g_message("Password entered while already authenticated");
+ }
+}
+
+
+/* Select the Password input if the Tab Key is Pressed */
+gboolean handle_tab_key(GtkWidget *widget, GdkEvent *event, App *app)
+{
+ (void) widget; // Window accessible through app.
+
+ GdkEventKey *key_event = (GdkEventKey *) event;
+ if (event->type == GDK_KEY_PRESS && key_event->keyval == GDK_KEY_Tab) {
+ g_message("Handling Tab Key Press");
+ gtk_window_present(GTK_WINDOW(APP_MAIN_WINDOW(app)));
+ gtk_widget_grab_focus(GTK_WIDGET(APP_PASSWORD_INPUT(app)));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Shutdown, Restart, Hibernate, Suspend, or Switch Sessions if the correct
+ * keys are pressed.
+ */
+gboolean handle_hotkeys(GtkWidget *widget, GdkEventKey *event, App *app)
+{
+ (void) widget;
+ Config *config = app->config;
+ FocusRing *sessions = app->session_ring;
+
+ if (event->state & config->mod_bit) {
+ if (event->keyval == config->suspend_key && lightdm_get_can_suspend()) {
+ lightdm_suspend(NULL);
+ } else if (event->keyval == config->hibernate_key &&
+ lightdm_get_can_hibernate()) {
+ lightdm_hibernate(NULL);
+ } else if (event->keyval == config->restart_key &&
+ lightdm_get_can_restart()) {
+ lightdm_restart(NULL);
+ } else if (event->keyval == config->shutdown_key &&
+ lightdm_get_can_shutdown()) {
+ lightdm_shutdown(NULL);
+ } else if (event->keyval == config->session_key && sessions != NULL) {
+ gchar *new_session = focus_ring_next(sessions);
+ set_ui_feedback_label(app, new_session);
+ } else {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/** Determine the current time & update the time GtkLabel.
+ */
+gboolean handle_time_update(App *app)
+{
+ time_t now = time(NULL);
+ struct tm *local_now = localtime(&now);
+ gchar date_string[30];
+ strftime(date_string, 29, "%H:%M", local_now);
+ gtk_label_set_text(GTK_LABEL(APP_TIME_LABEL(app)), date_string);
+
+ return TRUE;
+}
+
+/* Set the Feedback Label's text & ensure it is visible. */
+static void set_ui_feedback_label(App *app, gchar *feedback_text)
+{
+ if (!gtk_widget_get_visible(APP_FEEDBACK_LABEL(app))) {
+ gtk_widget_show(APP_FEEDBACK_LABEL(app));
+ }
+ gtk_label_set_text(GTK_LABEL(APP_FEEDBACK_LABEL(app)), feedback_text);
+}
diff --git a/src/callbacks.h b/src/callbacks.h
new file mode 100644
index 0000000..2b18a54
--- /dev/null
+++ b/src/callbacks.h
@@ -0,0 +1,15 @@
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <lightdm.h>
+
+#include "app.h"
+
+
+void authentication_complete_cb(LightDMGreeter *greeter, App *app);
+void handle_password(GtkWidget *password_input, App *app);
+gboolean handle_tab_key(GtkWidget *widget, GdkEvent *event, App *app);
+gboolean handle_hotkeys(GtkWidget *widget, GdkEventKey *event, App *app);
+gboolean handle_time_update(App *app);
+
+#endif
diff --git a/src/compat.c b/src/compat.c
new file mode 100644
index 0000000..74b683d
--- /dev/null
+++ b/src/compat.c
@@ -0,0 +1,25 @@
+/* Backwards-Compatible Functions for LightDM */
+#include <lightdm.h>
+
+#include "compat.h"
+
+gboolean compat_greeter_authenticate(LightDMGreeter *greeter, const gchar *username, GError **error)
+{
+#ifdef LIGHTDM_1_19_1_LOWER
+ lightdm_greeter_authenticate(greeter, username);
+ return TRUE;
+#else
+ return lightdm_greeter_authenticate(greeter, username, error);
+#endif
+}
+
+gboolean compat_greeter_respond(LightDMGreeter *greeter, const gchar *response, GError **error)
+{
+#ifdef LIGHTDM_1_19_1_LOWER
+ lightdm_greeter_respond(greeter, response);
+ return TRUE;
+#else
+ return lightdm_greeter_respond(greeter, response, error);
+#endif
+
+}
diff --git a/src/compat.h b/src/compat.h
new file mode 100644
index 0000000..10546fa
--- /dev/null
+++ b/src/compat.h
@@ -0,0 +1,12 @@
+#ifndef COMPAT_H
+#define COMPAT_H
+
+#include <lightdm.h>
+
+#include "defines.h"
+
+// v1.19.2 of LightDM introduced GError arguments but Debian jessie & stretch are not updated yet
+gboolean compat_greeter_authenticate(LightDMGreeter *greeter, const gchar *username, GError **error);
+gboolean compat_greeter_respond(LightDMGreeter *greeter, const gchar *response, GError **error);
+
+#endif
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..0d7b21b
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,435 @@
+/* Functions related to the Configuration */
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk/gdk.h>
+#include <glib.h>
+
+#include "config.h"
+#include "utils.h"
+
+
+static gchar *parse_greeter_string(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gchar *fallback);
+static gint parse_greeter_integer(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gint fallback);
+static gboolean parse_greeter_boolean(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gboolean fallback);
+static GdkRGBA *parse_greeter_color_key(GKeyFile *keyfile, const char *key_name, const char *default_str);
+static guint parse_greeter_hotkey_keyval(GKeyFile *keyfile, const char *key_name, const char default_char);
+static gunichar *parse_greeter_password_char(GKeyFile *keyfile);
+static gfloat parse_greeter_password_alignment(GKeyFile *keyfile);
+static gboolean is_rtl_keymap_layout(void);
+gboolean input_string_equals(gchar *input_str, const gchar * const fixed_str);
+
+/* Initialize the configuration, sourcing the greeter's configuration file */
+Config *initialize_config(void)
+{
+ Config *config = malloc(sizeof(Config));
+ if (config == NULL) {
+ g_error("Could not allocate memory for Config");
+ }
+
+ // Load the key-value file
+ GKeyFile *keyfile = g_key_file_new();
+ GError *keyerror = NULL;
+ gboolean keyfile_loaded = g_key_file_load_from_file(
+ keyfile, CONFIG_FILE, G_KEY_FILE_NONE, &keyerror);
+ if (!keyfile_loaded) {
+ if (keyerror != NULL) {
+ g_error("Could not load configuration file: %s", keyerror->message);
+ free(keyerror);
+ } else {
+ g_error("Could not load configuration file.");
+ }
+ }
+
+ // Parse values from the keyfile into a Config.
+ config->login_user =
+ g_strchomp(g_key_file_get_string(keyfile, "greeter", "user", NULL));
+ if (strcmp(config->login_user, "CHANGE_ME") == 0) {
+ g_message("User configuration value is unchanged.");
+ }
+ config->show_password_label =
+ parse_greeter_boolean(keyfile, "greeter", "show-password-label", TRUE);
+ config->password_label_text = parse_greeter_string(
+ keyfile, "greeter", "password-label-text", "Password:");
+ config->invalid_password_text = parse_greeter_string(
+ keyfile, "greeter", "invalid-password-text", "Invalid Password");
+ config->show_input_cursor =
+ parse_greeter_boolean(keyfile, "greeter", "show-input-cursor", TRUE);
+ config->password_alignment = parse_greeter_password_alignment(keyfile);
+ config->password_input_width = parse_greeter_integer(
+ keyfile, "greeter", "password-input-width", -1);
+ config->show_image_on_all_monitors = parse_greeter_boolean(
+ keyfile, "greeter", "show-image-on-all-monitors", FALSE);
+ config->show_sys_info = parse_greeter_boolean(
+ keyfile, "greeter", "show-sys-info", FALSE);
+
+ // Parse Hotkey Settings
+ config->suspend_key = parse_greeter_hotkey_keyval(keyfile, "suspend-key", 'u');
+ config->hibernate_key = parse_greeter_hotkey_keyval(keyfile, "hibernate-key", 'h');
+ config->restart_key = parse_greeter_hotkey_keyval(keyfile, "restart-key", 'r');
+ config->shutdown_key = parse_greeter_hotkey_keyval(keyfile, "shutdown-key", 's');
+ config->session_key = parse_greeter_hotkey_keyval(keyfile, "session-key", 'e');
+ gchar *mod_key =
+ g_key_file_get_string(keyfile, "greeter-hotkeys", "mod-key", NULL);
+ if (mod_key == NULL) {
+ config->mod_bit = GDK_SUPER_MASK;
+ } else {
+ g_strchomp(mod_key);
+ if (strcmp(mod_key, "control") == 0) {
+ config->mod_bit = GDK_CONTROL_MASK;
+ } else if (strcmp(mod_key, "alt") == 0) {
+ config->mod_bit = GDK_MOD1_MASK;
+ } else if (strcmp(mod_key, "meta") == 0) {
+ config->mod_bit = GDK_SUPER_MASK;
+ } else {
+ g_error("Invalid mod-key configuration value: '%s'\n", mod_key);
+ }
+ }
+
+ // Parse Theme Settings
+ // Font
+ config->font =
+ parse_greeter_string(keyfile, "greeter-theme", "font", "Sans");
+ config->font_size =
+ parse_greeter_string(keyfile, "greeter-theme", "font-size", "1em");
+ config->font_weight =
+ parse_greeter_string(keyfile, "greeter-theme", "font-weight", "bold");
+ config->font_style =
+ parse_greeter_string(keyfile, "greeter-theme", "font-style", "normal");
+ config->text_color =
+ parse_greeter_color_key(keyfile, "text-color", "#080800");
+ config->error_color =
+ parse_greeter_color_key(keyfile, "error-color", "#F8F8F0");
+ // Background
+ config->background_image =
+ g_key_file_get_string(keyfile, "greeter-theme", "background-image", NULL);
+ if (config->background_image == NULL || strcmp(config->background_image, "") == 0) {
+ config->background_image = (gchar *) "\"\"";
+ }
+ config->background_color =
+ parse_greeter_color_key(keyfile, "background-color", "#1B1D1E");
+ config->background_image_size =
+ parse_greeter_string(keyfile, "greeter-theme", "background-image-size", "auto");
+ // Window
+ config->window_color =
+ parse_greeter_color_key(keyfile, "window-color", "#F92672");
+ config->border_color =
+ parse_greeter_color_key(keyfile, "border-color", "#080800");
+ config->border_width = parse_greeter_string(
+ keyfile, "greeter-theme", "border-width", "2px");
+ // Password
+ config->password_char =
+ parse_greeter_password_char(keyfile);
+ config->password_color =
+ parse_greeter_color_key(keyfile, "password-color", "#F8F8F0");
+ config->password_background_color =
+ parse_greeter_color_key(keyfile, "password-background-color", "#1B1D1E");
+ gchar *temp_password_border_color = g_key_file_get_string(
+ keyfile, "greeter-theme", "password-border-color", NULL);
+ if (temp_password_border_color == NULL) {
+ config->password_border_color = config->border_color;
+ } else {
+ free(temp_password_border_color);
+ config->password_border_color =
+ parse_greeter_color_key(keyfile, "password-border-color", "#080800");
+ }
+ config->password_border_width = parse_greeter_string(
+ keyfile, "greeter-theme", "password-border-width", config->border_width);
+ config->password_border_radius = parse_greeter_string(
+ keyfile, "greeter-theme", "password-border-radius", "0.341125em");
+ // System Info
+ gchar *temp_sys_info_color = g_key_file_get_string(
+ keyfile, "greeter-theme", "sys-info-color", NULL);
+ if (temp_sys_info_color == NULL) {
+ config->sys_info_color = config->text_color;
+ } else {
+ config->sys_info_color = parse_greeter_color_key(
+ keyfile, "sys-info-color", "#080800");
+ }
+ config->sys_info_font = parse_greeter_string(keyfile, "greeter-theme", "sys-info-font", config->font);
+ config->sys_info_font_size =
+ parse_greeter_string(keyfile, "greeter-theme", "sys-info-font-size", config->font_size);
+ config->sys_info_margin =
+ parse_greeter_string(keyfile, "greeter-theme", "sys-info-margin", "-5px -5px -5px");
+
+
+ gint layout_spacing =
+ parse_greeter_integer(keyfile, "greeter-theme", "layout-space", 15);
+ if (layout_spacing < 0) {
+ config->layout_spacing = (guint) (-1 * layout_spacing);
+ } else {
+ config->layout_spacing = (guint) layout_spacing;
+ }
+
+ config->x_pos = g_key_file_get_double(keyfile, "greeter-theme", "x-pos", NULL);
+ config->y_pos = g_key_file_get_double(keyfile, "greeter-theme", "y-pos", NULL);
+
+ g_key_file_free(keyfile);
+
+ return config;
+}
+
+
+/* Cleanup any memory allocated for the Config */
+void destroy_config(Config *config)
+{
+ free(config->login_user);
+ free(config->font);
+ free(config->font_size);
+ free(config->font_weight);
+ free(config->font_style);
+ free(config->text_color);
+ free(config->error_color);
+ free(config->background_image);
+ free(config->background_color);
+ free(config->background_image_size);
+ free(config->window_color);
+ free(config->border_color);
+ free(config->border_width);
+ free(config->password_label_text);
+ free(config->invalid_password_text);
+ free(config->password_char);
+ free(config->password_color);
+ free(config->password_background_color);
+ free(config->password_border_color);
+ free(config->password_border_width);
+ free(config->password_border_radius);
+ free(config->sys_info_color);
+ free(config->sys_info_font_size);
+ free(config->sys_info_margin);
+ free(config);
+}
+
+
+/* Parse a string from the config file, returning a copy of the fallback value
+ * if the key is not present in the group.
+ */
+static gchar *parse_greeter_string(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gchar *fallback)
+{
+ gchar *parsed_string = g_key_file_get_string(keyfile, group_name, key_name, NULL);
+ if (parsed_string == NULL) {
+ g_warning("Could not find value for %s.%s - falling back to '%s'",
+ group_name, key_name, fallback);
+ return g_strdup(fallback);
+ } else {
+ return parsed_string;
+ }
+}
+
+/* Parse an integer from the config file, returning the fallback value if the
+ * key is not present in the group, or if the value is not an integer.
+ */
+static gint parse_greeter_integer(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gint fallback)
+{
+ GError *parse_error = NULL;
+ gint parse_result = g_key_file_get_integer(
+ keyfile, group_name, key_name, &parse_error);
+ if (parse_error != NULL) {
+ if (parse_error->code == G_KEY_FILE_ERROR_INVALID_VALUE) {
+ // Read the value as a string so we can log it
+ gchar *value = g_key_file_get_string(
+ keyfile, group_name, key_name, NULL);
+ g_warning("Invalid integer for %s.%s: `%s`",
+ group_name, key_name, value);
+ free(value);
+ }
+ g_error_free(parse_error);
+ return fallback;
+ }
+ return parse_result;
+}
+
+/* Parse a boolean from the config file, returning the fallback value if the
+ * key is not present in the group or cannot be parsed as a boolean.
+ */
+static gboolean parse_greeter_boolean(GKeyFile *keyfile, const char *group_name,
+ const char *key_name, const gboolean fallback)
+{
+ GError *parse_error = NULL;
+ gboolean parse_result = g_key_file_get_boolean(
+ keyfile, group_name, key_name, &parse_error);
+ if (!parse_result && parse_error != NULL) {
+ if (parse_error->code == G_KEY_FILE_ERROR_INVALID_VALUE) {
+ // Read the value as a string so we can log it
+ gchar *value = g_key_file_get_string(
+ keyfile, group_name, key_name, NULL);
+ g_warning("Invalid boolean for %s.%s: `%s`\n",
+ group_name, key_name, value);
+ g_free(value);
+ }
+ g_error_free(parse_error);
+ return fallback;
+ }
+ return parse_result;
+}
+
+/* Parse a greeter-colors group key into a newly-allocated GdkRGBA value */
+static GdkRGBA *parse_greeter_color_key(GKeyFile *keyfile, const char *key_name, const char *default_str)
+{
+ gchar *color_string = g_key_file_get_string(
+ keyfile, "greeter-theme", key_name, NULL);
+ if (color_string == NULL) {
+ g_warning("Could not find value for %s.%s - falling back to '%s'",
+ "greeter-theme", key_name, default_str);
+ }
+
+ GdkRGBA *default_color = malloc(sizeof(GdkRGBA));
+ gboolean default_was_parsed = gdk_rgba_parse(default_color, default_str);
+ if (!default_was_parsed) {
+ g_critical("Could not parse the default '%s' setting: %s", key_name, default_str);
+ }
+
+ if (color_string != NULL) {
+ if (strstr(color_string, "#") != NULL) {
+ // Remove quotations from hex color strings
+ remove_char(color_string, '"');
+ remove_char(color_string, '\'');
+ }
+
+ GdkRGBA *color = malloc(sizeof(GdkRGBA));
+ gboolean color_was_parsed = gdk_rgba_parse(color, color_string);
+ if (color_was_parsed) {
+ free(default_color);
+ return color;
+ }
+ g_warning("Could not parse the '%s' setting: %s - falling back to default of %s",
+ key_name, color_string, default_str);
+ free(color);
+ }
+ return default_color;
+}
+
+/* Parse a greeter-hotkeys key into the GDKkeyval of it's first character */
+static guint parse_greeter_hotkey_keyval(GKeyFile *keyfile, const char *key_name, const char default_char)
+{
+ gchar *key = g_key_file_get_string(
+ keyfile, "greeter-hotkeys", key_name, NULL);
+
+ guint32 key_code;
+ if (key == NULL) {
+ key_code = (guint32) default_char;
+ } else if (strcmp(key, "") == 0) {
+ g_warning("Configuration contains empty key for '%s' - falling back to default of %c\n", key_name, default_char);
+ key_code = (guint32) default_char;
+ } else {
+ key_code = (guint32) key[0];
+ }
+
+ return gdk_unicode_to_keyval(key_code);
+}
+
+/* Parse the password masking character that should be displayed when typing
+ * into the password input.
+ *
+ * We first attempt to parse a literal -1 or 0, where -1 means to use the
+ * default character & 0 means to display no characters when typing a password.
+ *
+ * If that parsing fails, we attempt to parse the field as a string & use the
+ * first character of the string. If the string is empty or parsing fails, we
+ * fall back to the default characer.
+ *
+ * Since the related gtk_entry function takes a unsigned int, we use pointers,
+ * where NULL means "use the default" & all other values indicate an argument
+ * to the `gtk_entry_set_invisible_char` function.
+ *
+ */
+static gunichar *parse_greeter_password_char(GKeyFile *keyfile)
+{
+ const char *const group_name = "greeter-theme";
+ const char *const key_name = "password-character";
+ GError *parse_error = NULL;
+
+ // Attempt the int parsing
+
+ const gint int_result = g_key_file_get_integer(
+ keyfile, group_name, key_name, &parse_error);
+ // Matches -1
+ if (int_result == -1) {
+ return NULL;
+ }
+
+ gunichar *result = malloc(sizeof(gunichar));
+ // Matches 0
+ if (int_result == 0 && parse_error == NULL) {
+ *result = 0;
+ return result;
+ } else if (parse_error != NULL) {
+ g_error_free(parse_error);
+ }
+
+ // Atempt the string parsing
+
+ gchar *str_result = g_key_file_get_string(
+ keyfile, group_name, key_name, NULL);
+
+ // Invalid or 0-length string
+ if (str_result == NULL || strlen(str_result) == 0) {
+ if (str_result != NULL) {
+ free(str_result);
+ }
+ free(result);
+ return NULL;
+ }
+
+ // Convert to unicode code points
+ gunichar *unicode_str = g_utf8_to_ucs4(str_result, -1, NULL, NULL, NULL);
+ free(str_result);
+
+ if (unicode_str == NULL) {
+ free(result);
+ return NULL;
+ }
+
+ *result = unicode_str[0];
+ g_free(unicode_str);
+ return result;
+}
+
+/* Parse the password input alignment, properly handling RTL layouts.
+ *
+ * Note that the gboolean returned by this function is meant to be used with
+ * the `gtk_entry_set_alignment` function.
+ */
+static gfloat parse_greeter_password_alignment(GKeyFile *keyfile)
+{
+ gfloat alignment;
+
+ gchar *password_alignment_text = parse_greeter_string(
+ keyfile, "greeter", "password-alignment", "right");
+ gboolean is_rtl = is_rtl_keymap_layout();
+
+ if (input_string_equals(password_alignment_text, "left")) {
+ alignment = is_rtl ? 1 : 0;
+ } else if (input_string_equals(password_alignment_text, "center")) {
+ alignment = 0.5;
+ } else {
+ alignment = is_rtl ? 0 : 1;
+ }
+ free(password_alignment_text);
+ return alignment;
+}
+
+/* Determine if the default Display's Keymap is in the Right-to-Left direction
+ */
+static gboolean is_rtl_keymap_layout(void)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ if (display == NULL) {
+ return FALSE;
+ }
+ GdkKeymap *keymap = gdk_keymap_get_for_display(display);
+ PangoDirection text_direction = gdk_keymap_get_direction(keymap);
+ return text_direction == PANGO_DIRECTION_RTL;
+}
+
+/* Take a string from the config file, trim any whitespace & check it's
+ * equality with a fixed string.
+ */
+gboolean input_string_equals(gchar *input_str, const gchar * const fixed_str) {
+ return strcmp(g_strchomp(input_str), fixed_str) == 0;
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..51e0f26
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,68 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <gdk/gdk.h>
+#include <glib.h>
+
+#ifndef CONFIG_FILE
+#define CONFIG_FILE "/etc/lightdm/lightdm-mini-greeter.conf"
+#endif
+
+
+// Represents the System's Greeter Configuration. Parsed from `CONFIG_FILE`.
+typedef struct Config_ {
+ gchar *login_user;
+ gboolean show_password_label;
+ gchar *password_label_text;
+ gchar *invalid_password_text;
+ gboolean show_input_cursor;
+ gfloat password_alignment;
+ gint password_input_width;
+ gboolean show_image_on_all_monitors;
+ gboolean show_sys_info;
+
+ /* Theme Configuration */
+ gchar *font;
+ gchar *font_size;
+ gchar *font_weight;
+ gchar *font_style;
+ GdkRGBA *text_color;
+ GdkRGBA *error_color;
+ // Windows
+ gchar *background_image;
+ GdkRGBA *background_color;
+ gchar *background_image_size;
+ GdkRGBA *window_color;
+ GdkRGBA *border_color;
+ gchar *border_width;
+ guint layout_spacing;
+ // Window position
+ gdouble x_pos;
+ gdouble y_pos;
+ // Password Input
+ gunichar *password_char;
+ GdkRGBA *password_color;
+ GdkRGBA *password_background_color;
+ GdkRGBA *password_border_color;
+ gchar *password_border_width;
+ gchar *password_border_radius;
+ // System Info
+ gchar *sys_info_font;
+ gchar *sys_info_font_size;
+ GdkRGBA *sys_info_color;
+ gchar *sys_info_margin;
+
+ /* Hotkeys */
+ guint mod_bit;
+ guint shutdown_key;
+ guint restart_key;
+ guint hibernate_key;
+ guint suspend_key;
+ guint session_key;
+} Config;
+
+
+Config *initialize_config(void);
+void destroy_config(Config *config);
+
+#endif
diff --git a/src/focus_ring.c b/src/focus_ring.c
new file mode 100644
index 0000000..45f64b2
--- /dev/null
+++ b/src/focus_ring.c
@@ -0,0 +1,105 @@
+/* Functions related to a ring of focusable items */
+
+#include "focus_ring.h"
+
+static gint find_by_value(gconstpointer data, gconstpointer user_data);
+
+
+/* Initialize the FocusRing with the given options, value getter, & label
+ *
+ * Returns NULL if the passed GList is NULL.
+ * Throws an error if cannot allocate memory for the FocusRing.
+ */
+FocusRing *initialize_focus_ring(const GList *options, gchar *(*getter_function)(gconstpointer), const gchar *const label)
+{
+ if (options == NULL) {
+ return NULL;
+ }
+
+ FocusRing *ring = malloc(sizeof(FocusRing));
+ if (ring == NULL) {
+ g_error("Could not allocate memory for FocusRing: %s", label);
+ }
+
+ ring->selected = options;
+ ring->beginning = options;
+ ring->end = g_list_last((GList *) options);
+
+ ring->getter_function = getter_function;
+
+ return ring;
+}
+
+/* Free only the FocusRing & not the underlying GList */
+void destroy_focus_ring(FocusRing *ring)
+{
+ free(ring);
+}
+
+/* Focus the next item in the ring, or loop back to the first item. */
+gchar *focus_ring_next(FocusRing *ring)
+{
+ if (ring->selected->next == NULL) {
+ ring->selected = ring->beginning;
+ } else {
+ ring->selected = ring->selected->next;
+ }
+ return focus_ring_get_value(ring);
+}
+
+/* Focus the previous item in the ring, or loop back to the last item. */
+gchar *focus_ring_prev(FocusRing *ring)
+{
+ if (ring->selected->prev == NULL) {
+ ring->selected = ring->end;
+ } else {
+ ring->selected = ring->selected->prev;
+ }
+ return focus_ring_get_value(ring);
+}
+
+/* Get the currently selected data. */
+gconstpointer focus_ring_get_selected(FocusRing *ring)
+{
+ return ring->selected->data;
+}
+
+/* Get the inner value of the currently selected item. */
+gchar *focus_ring_get_value(FocusRing *ring)
+{
+ return ring->getter_function(focus_ring_get_selected(ring));
+}
+
+
+/* Internal data used by the find_by_value & focus_ring_scroll_to_value
+ * functions.
+ */
+struct FindByValueData {
+ FocusRing *ring;
+ const gchar *target_value;
+};
+
+/* Attempt to scroll the ring to the item with the matching inner value.
+ *
+ * Does nothing if the item cannot be found.
+ */
+gchar *focus_ring_scroll_to_value(FocusRing *ring, const gchar *target_value)
+{
+ struct FindByValueData user_data = { .ring = ring, .target_value = target_value };
+ GList *find_result = g_list_find_custom((GList *) ring->beginning, (void *) &user_data, &find_by_value);
+ if (find_result != NULL) {
+ ring->selected = find_result;
+ }
+ return focus_ring_get_value(ring);
+}
+
+/* Determine if the given data's inner value matches what we're looking for.
+ *
+ * Used as callback to the g_list_find_custom function.
+ */
+static gint find_by_value(gconstpointer data, gconstpointer user_data)
+{
+ struct FindByValueData *local_user_data = (struct FindByValueData *) user_data;
+
+ return g_strcmp0(local_user_data->ring->getter_function(data), local_user_data->target_value);
+}
diff --git a/src/focus_ring.h b/src/focus_ring.h
new file mode 100644
index 0000000..74e9002
--- /dev/null
+++ b/src/focus_ring.h
@@ -0,0 +1,31 @@
+#ifndef FOCUSRING_H
+#define FOCUSRING_H
+
+#include <gdk/gdk.h>
+
+
+/* A FocusRing is an scrollable list that wraps around to the beginning/end
+ * when the selection is moved out-of-bounds.
+ */
+typedef struct FocusRing_ {
+ /* Points to current selection */
+ const GList *selected;
+ /* Points to beginning of list */
+ const GList *beginning;
+ /* Points to end of list */
+ const GList *end;
+
+ /* Function to retrieve the target "inner value" from the GList's data */
+ gchar *(*getter_function)(gconstpointer);
+} FocusRing;
+
+FocusRing *initialize_focus_ring(const GList *options, gchar *(*getter_function)(gconstpointer), const gchar *const label);
+void destroy_focus_ring(FocusRing *ring);
+
+gchar *focus_ring_next(FocusRing *ring);
+gchar *focus_ring_prev(FocusRing *ring);
+gconstpointer focus_ring_get_selected(FocusRing *ring);
+gchar *focus_ring_get_value(FocusRing *ring);
+gchar *focus_ring_scroll_to_value(FocusRing *ring, const gchar *target_value);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..99351c9
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,28 @@
+/* lightdm-mini-greeter - A minimal GTK LightDM Greeter */
+#include <sys/mman.h>
+
+#include <gtk/gtk.h>
+
+#include "app.h"
+#include "utils.h"
+
+
+int main(int argc, char **argv)
+{
+ mlockall(MCL_CURRENT | MCL_FUTURE); // Keep data out of any swap devices
+
+ App *app = initialize_app(argc, argv);
+
+ connect_to_lightdm_daemon(app->greeter);
+ begin_authentication_as_default_user(app);
+ make_session_focus_ring(app);
+
+ for (int m = 0; m < APP_MONITOR_COUNT(app); m++) {
+ gtk_widget_show_all(GTK_WIDGET(APP_BACKGROUND_WINDOWS(app)[m]));
+ }
+ gtk_widget_show_all(GTK_WIDGET(APP_MAIN_WINDOW(app)));
+ gtk_window_present(APP_MAIN_WINDOW(app));
+ gtk_main();
+
+ destroy_app(app);
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..6d2d42c
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,430 @@
+/* Functions related to the GUI. */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <lightdm.h>
+
+#include "callbacks.h"
+#include "ui.h"
+#include "utils.h"
+
+
+static UI *new_ui(void);
+static void setup_background_windows(Config *config, UI *ui);
+static GtkWindow *new_background_window(GdkMonitor *monitor);
+static void set_window_to_monitor_size(GdkMonitor *monitor, GtkWindow *window);
+static void hide_mouse_cursor(GtkWidget *window, gpointer user_data);
+static void move_mouse_to_background_window(void);
+static void setup_main_window(Config *config, UI *ui);
+static void place_main_window(GtkWidget *main_window, gpointer user_data);
+static void create_and_attach_layout_container(UI *ui);
+static void create_and_attach_sys_info_label(Config *config, UI *ui);
+static void create_and_attach_password_field(Config *config, UI *ui);
+static void create_and_attach_feedback_label(UI *ui);
+static void attach_config_colors_to_screen(Config *config);
+
+
+/* Initialize the Main Window & it's Children */
+UI *initialize_ui(Config *config)
+{
+ UI *ui = new_ui();
+
+ setup_background_windows(config, ui);
+ move_mouse_to_background_window();
+ setup_main_window(config, ui);
+ create_and_attach_layout_container(ui);
+ create_and_attach_sys_info_label(config, ui);
+ create_and_attach_password_field(config, ui);
+ create_and_attach_feedback_label(ui);
+ attach_config_colors_to_screen(config);
+
+ return ui;
+}
+
+
+/* Create a new UI with all values initialized to NULL */
+static UI *new_ui(void)
+{
+ UI *ui = malloc(sizeof(UI));
+ if (ui == NULL) {
+ g_error("Could not allocate memory for UI");
+ }
+ ui->background_windows = NULL;
+ ui->monitor_count = 0;
+ ui->main_window = NULL;
+ ui->layout_container = NULL;
+ ui->password_label = NULL;
+ ui->password_input = NULL;
+ ui->feedback_label = NULL;
+
+ return ui;
+}
+
+
+/* Create a Background Window for Every Monitor */
+static void setup_background_windows(Config *config, UI *ui)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ ui->monitor_count = gdk_display_get_n_monitors(display);
+ ui->background_windows = malloc((uint) ui->monitor_count * sizeof (GtkWindow *));
+ for (int m = 0; m < ui->monitor_count; m++) {
+ GdkMonitor *monitor = gdk_display_get_monitor(display, m);
+ if (monitor == NULL) {
+ break;
+ }
+
+ GtkWindow *background_window = new_background_window(monitor);
+ ui->background_windows[m] = background_window;
+
+ gboolean show_background_image =
+ (gdk_monitor_is_primary(monitor) || config->show_image_on_all_monitors) &&
+ (strcmp(config->background_image, "\"\"") != 0);
+ if (show_background_image) {
+ GtkStyleContext *style_context =
+ gtk_widget_get_style_context(GTK_WIDGET(background_window));
+ gtk_style_context_add_class(style_context, "with-image");
+ }
+ }
+}
+
+
+/* Create & Configure a Background Window for a Monitor */
+static GtkWindow *new_background_window(GdkMonitor *monitor)
+{
+ GtkWindow *background_window = GTK_WINDOW(gtk_window_new(
+ GTK_WINDOW_TOPLEVEL));
+ gtk_window_set_type_hint(background_window, GDK_WINDOW_TYPE_HINT_DESKTOP);
+ gtk_window_set_keep_below(background_window, TRUE);
+ gtk_widget_set_name(GTK_WIDGET(background_window), "background");
+
+ // Set Window Size to Monitor Size
+ set_window_to_monitor_size(monitor, background_window);
+
+ g_signal_connect(background_window, "realize", G_CALLBACK(hide_mouse_cursor),
+ NULL);
+ // TODO: is this needed?
+ g_signal_connect(background_window, "destroy", G_CALLBACK(gtk_main_quit),
+ NULL);
+
+ return background_window;
+}
+
+
+/* Set the Window's Minimum Size to the Default Screen's Size */
+static void set_window_to_monitor_size(GdkMonitor *monitor, GtkWindow *window)
+{
+ GdkRectangle geometry;
+ gdk_monitor_get_geometry(monitor, &geometry);
+ gtk_widget_set_size_request(
+ GTK_WIDGET(window),
+ geometry.width,
+ geometry.height
+ );
+ gtk_window_move(window, geometry.x, geometry.y);
+ gtk_window_set_resizable(window, FALSE);
+}
+
+
+/* Hide the mouse cursor when it is hovered over the given widget.
+ *
+ * Note: This has no effect when used with a GtkEntry widget.
+ */
+static void hide_mouse_cursor(GtkWidget *widget, gpointer user_data)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ GdkCursor *blank_cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if (window != NULL) {
+ gdk_window_set_cursor(window, blank_cursor);
+ }
+}
+
+
+/* Move the mouse cursor to the upper-left corner of the primary screen.
+ *
+ * This is necessary for hiding the mouse cursor because we cannot hide the
+ * mouse cursor when it is hovered over the GtkEntry password input. Instead,
+ * we hide the cursor when it is over the background windows and then move the
+ * mouse to the corner of the screen where it should hover over the background
+ * window or main window instead.
+ */
+static void move_mouse_to_background_window(void)
+{
+ GdkDisplay *display = gdk_display_get_default();
+ GdkDevice *mouse = gdk_seat_get_pointer(gdk_display_get_default_seat(display));
+ GdkScreen *screen = gdk_display_get_default_screen(display);
+
+ gdk_device_warp(mouse, screen, 0, 0);
+}
+
+
+/* Create & Configure the Main Window */
+static void setup_main_window(Config *config, UI *ui)
+{
+ GtkWindow *main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+
+ gtk_container_set_border_width(GTK_CONTAINER(main_window), config->layout_spacing);
+ gtk_widget_set_name(GTK_WIDGET(main_window), "main");
+
+ g_signal_connect(main_window, "show", G_CALLBACK(place_main_window), config);
+ g_signal_connect(main_window, "realize", G_CALLBACK(hide_mouse_cursor), NULL);
+ g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+
+ ui->main_window = main_window;
+}
+
+
+/* Move the Main Window to the Center of the Primary Monitor
+ *
+ * This is done after the main window is shown(via the "show" signal) so that
+ * the width of the window is properly calculated. Otherwise the returned size
+ * will not include the size of the password label text.
+ */
+static void place_main_window(GtkWidget *main_window, gpointer user_data)
+{
+ // Get the Geometry of the Primary Monitor
+ GdkDisplay *display = gdk_display_get_default();
+ GdkMonitor *primary_monitor = gdk_display_get_primary_monitor(display);
+ GdkRectangle primary_monitor_geometry;
+ gdk_monitor_get_geometry(primary_monitor, &primary_monitor_geometry);
+
+ // Get the Geometry of the Window
+ gint window_width, window_height;
+ gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height);
+
+ Config *config = (Config *) user_data;
+ gint xpos = (gint) (primary_monitor_geometry.width * config->x_pos);
+ gint ypos = (gint) (primary_monitor_geometry.height * config->y_pos);
+
+ gtk_window_move(
+ GTK_WINDOW(main_window),
+ primary_monitor_geometry.x + xpos - window_width / 2,
+ primary_monitor_geometry.y + ypos - window_height / 2);
+}
+
+
+/* Add a Layout Container for All Displayed Widgets */
+static void create_and_attach_layout_container(UI *ui)
+{
+ ui->layout_container = GTK_GRID(gtk_grid_new());
+ gtk_grid_set_column_spacing(ui->layout_container, 5);
+ gtk_grid_set_row_spacing(ui->layout_container, 5);
+
+ gtk_container_add(GTK_CONTAINER(ui->main_window),
+ GTK_WIDGET(ui->layout_container));
+}
+
+/* Create a container for the system information & current time.
+ *
+ * Set the system information text by querying LightDM & the Config, but leave
+ * the time blank & let the timer update it.
+ */
+static void create_and_attach_sys_info_label(Config *config, UI *ui)
+{
+ if (!config->show_sys_info) {
+ return;
+ }
+ // container for system info & time
+ ui->info_container = GTK_GRID(gtk_grid_new());
+ gtk_grid_set_column_spacing(ui->info_container, 0);
+ gtk_grid_set_row_spacing(ui->info_container, 5);
+ gtk_widget_set_name(GTK_WIDGET(ui->info_container), "info");
+
+ // system info: <user>@<hostname>
+ const gchar *hostname = lightdm_get_hostname();
+ gchar *output_string;
+ int output_string_length = asprintf(&output_string, "%s@%s",
+ config->login_user, hostname);
+ if (output_string_length >= 0) {
+ ui->sys_info_label = gtk_label_new(output_string);
+ } else {
+ g_warning("Could not allocate memory for system info string.");
+ ui->sys_info_label = gtk_label_new("");
+ }
+ gtk_label_set_xalign(GTK_LABEL(ui->sys_info_label), 0.0f);
+ gtk_widget_set_name(GTK_WIDGET(ui->sys_info_label), "sys-info");
+
+ // time: filled out by callback
+ ui->time_label = gtk_label_new("");
+ gtk_label_set_xalign(GTK_LABEL(ui->time_label), 1.0f);
+ gtk_widget_set_hexpand(GTK_WIDGET(ui->time_label), TRUE);
+ gtk_widget_set_name(GTK_WIDGET(ui->time_label), "time-info");
+
+ // attach labels to info container, attach info container to layout.
+ gtk_grid_attach(
+ ui->info_container, GTK_WIDGET(ui->sys_info_label), 0, 0, 1, 1);
+ gtk_grid_attach(
+ ui->info_container, GTK_WIDGET(ui->time_label), 1, 0, 1, 1);
+ gtk_grid_attach(
+ ui->layout_container, GTK_WIDGET(ui->info_container), 0, 0, 2, 1);
+}
+
+
+/* Add a label & entry field for the user's password.
+ *
+ * If the `show_password_label` member of `config` is FALSE,
+ * `ui->password_label` is left as NULL.
+ */
+static void create_and_attach_password_field(Config *config, UI *ui)
+{
+ ui->password_input = gtk_entry_new();
+ gtk_entry_set_visibility(GTK_ENTRY(ui->password_input), FALSE);
+ if (config->password_char != NULL) {
+ gtk_entry_set_invisible_char(GTK_ENTRY(ui->password_input), *config->password_char);
+ }
+ gtk_entry_set_alignment(GTK_ENTRY(ui->password_input),
+ config->password_alignment);
+ // TODO: The width is usually a little shorter than we specify. Is there a
+ // way to force this exact character width?
+ // Maybe use 2 GtkBoxes instead of a GtkGrid?
+ gtk_entry_set_width_chars(GTK_ENTRY(ui->password_input),
+ config->password_input_width);
+ gtk_widget_set_name(GTK_WIDGET(ui->password_input), "password");
+ const gint top = config->show_sys_info ? 1 : 0;
+ gtk_grid_attach(ui->layout_container, ui->password_input, 1, top, 1, 1);
+
+ if (config->show_password_label) {
+ ui->password_label = gtk_label_new(config->password_label_text);
+ gtk_label_set_justify(GTK_LABEL(ui->password_label), GTK_JUSTIFY_RIGHT);
+ gtk_grid_attach_next_to(ui->layout_container, ui->password_label,
+ ui->password_input, GTK_POS_LEFT, 1, 1);
+ }
+}
+
+
+/* Add a label for feedback to the user */
+static void create_and_attach_feedback_label(UI *ui)
+{
+ ui->feedback_label = gtk_label_new("");
+ gtk_label_set_justify(GTK_LABEL(ui->feedback_label), GTK_JUSTIFY_CENTER);
+ gtk_widget_set_no_show_all(ui->feedback_label, TRUE);
+ gtk_widget_set_name(GTK_WIDGET(ui->feedback_label), "error");
+
+ GtkWidget *attachment_point;
+ gint width;
+ if (ui->password_label == NULL) {
+ attachment_point = ui->password_input;
+ width = 1;
+ } else {
+ attachment_point = ui->password_label;
+ width = 2;
+ }
+
+ gtk_grid_attach_next_to(ui->layout_container, ui->feedback_label,
+ attachment_point, GTK_POS_BOTTOM, width, 1);
+}
+
+/* Attach a style provider to the screen, using color options from config */
+static void attach_config_colors_to_screen(Config *config)
+{
+ GtkCssProvider* provider = gtk_css_provider_new();
+
+ GdkRGBA *caret_color;
+ if (config->show_input_cursor) {
+ caret_color = config->password_color;
+ } else {
+ caret_color = config->password_background_color;
+ }
+
+ char *css;
+ int css_string_length = asprintf(&css,
+ "* {\n"
+ "font-family: %s;\n"
+ "font-size: %s;\n"
+ "font-weight: %s;\n"
+ "font-style: %s;\n"
+ "}\n"
+ "label {\n"
+ "color: %s;\n"
+ "}\n"
+ "label#error {\n"
+ "color: %s;\n"
+ "}\n"
+ "#background {\n"
+ "background-color: %s;\n"
+ "}\n"
+ "#background.with-image {\n"
+ "background-image: image(url(%s), %s);\n"
+ "background-repeat: no-repeat;\n"
+ "background-size: %s;\n"
+ "background-position: center;\n"
+ "}\n"
+ "#main, #password {\n"
+ "border-width: %s;\n"
+ "border-color: %s;\n"
+ "border-style: solid;\n"
+ "}\n"
+ "#main {\n"
+ "background-color: %s;\n"
+ "}\n"
+ "#password {\n"
+ "color: %s;\n"
+ "caret-color: %s;\n"
+ "background-color: %s;\n"
+ "border-width: %s;\n"
+ "border-color: %s;\n"
+ "border-radius: %s;\n"
+ "background-image: none;\n"
+ "box-shadow: none;\n"
+ "border-image-width: 0;\n"
+ "}\n"
+ "#info {\n"
+ "margin: %s;\n"
+ "}\n"
+ "#info label {\n"
+ "font-family: %s;\n"
+ "font-size: %s;\n"
+ "color: %s;\n"
+ "}\n"
+
+ // *
+ , config->font
+ , config->font_size
+ , config->font_weight
+ , config->font_style
+ // label
+ , gdk_rgba_to_string(config->text_color)
+ // label#error
+ , gdk_rgba_to_string(config->error_color)
+ // #background
+ , gdk_rgba_to_string(config->background_color)
+ // #background.image-background
+ , config->background_image
+ , gdk_rgba_to_string(config->background_color)
+ , config->background_image_size
+ // #main, #password
+ , config->border_width
+ , gdk_rgba_to_string(config->border_color)
+ // #main
+ , gdk_rgba_to_string(config->window_color)
+ // #password
+ , gdk_rgba_to_string(config->password_color)
+ , gdk_rgba_to_string(caret_color)
+ , gdk_rgba_to_string(config->password_background_color)
+ , config->password_border_width
+ , gdk_rgba_to_string(config->password_border_color)
+ , config->password_border_radius
+ // #info
+ , config->sys_info_margin
+ // #info label
+ , config->sys_info_font
+ , config->sys_info_font_size
+ , gdk_rgba_to_string(config->sys_info_color)
+ );
+
+ if (css_string_length >= 0) {
+ gtk_css_provider_load_from_data(provider, css, -1, NULL);
+
+ GdkScreen *screen = gdk_screen_get_default();
+ gtk_style_context_add_provider_for_screen(
+ screen, GTK_STYLE_PROVIDER(provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER + 1);
+ }
+
+
+ g_object_unref(provider);
+}
diff --git a/src/ui.h b/src/ui.h
new file mode 100644
index 0000000..3d84aaf
--- /dev/null
+++ b/src/ui.h
@@ -0,0 +1,24 @@
+#ifndef UI_H
+#define UI_H
+
+#include <gtk/gtk.h>
+#include "config.h"
+
+
+typedef struct UI_ {
+ GtkWindow **background_windows;
+ int monitor_count;
+ GtkWindow *main_window;
+ GtkGrid *layout_container;
+ GtkGrid *info_container;
+ GtkWidget *sys_info_label;
+ GtkWidget *time_label;
+ GtkWidget *password_label;
+ GtkWidget *password_input;
+ GtkWidget *feedback_label;
+} UI;
+
+
+UI *initialize_ui(Config *config);
+
+#endif
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..38a34de
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,70 @@
+/* General Utility Functions */
+#include <lightdm.h>
+
+#include "app.h"
+#include "compat.h"
+#include "lightdm/session.h"
+#include "utils.h"
+#include "focus_ring.h"
+
+static gchar *get_session_key(gconstpointer data);
+
+
+/* Connect to the LightDM daemon or exit with an error */
+void connect_to_lightdm_daemon(LightDMGreeter *greeter)
+{
+ if (!lightdm_greeter_connect_sync(greeter, NULL)) {
+ g_critical("Could not connect to the LightDM daemon");
+ }
+}
+
+
+/* Begin authentication as the default user, or exit with an error */
+void begin_authentication_as_default_user(App *app)
+{
+ const gchar *default_user = APP_LOGIN_USER(app);
+ if (g_strcmp0(default_user, NULL) == 0) {
+ g_critical("A default user has not been not set");
+ } else {
+ g_message("Beginning authentication as the default user: %s",
+ default_user);
+ compat_greeter_authenticate(app->greeter, default_user, NULL);
+ }
+}
+
+
+/* Remove every occurence of a character from a string */
+void remove_char(char *str, char garbage) {
+
+ char *src, *dst;
+ for (src = dst = str; *src != '\0'; src++) {
+ *dst = *src;
+ if (*dst != garbage) dst++;
+ }
+ *dst = '\0';
+}
+
+
+/* Get Sessions & Build the Focus Ring */
+void make_session_focus_ring(App *app)
+{
+ const gchar *default_session =
+ lightdm_greeter_get_default_session_hint(app->greeter);
+ const GList *sessions = lightdm_get_sessions();
+ FocusRing *session_ring = initialize_focus_ring(sessions, &get_session_key, "sessions");
+
+ if (default_session != NULL) {
+ focus_ring_scroll_to_value(session_ring, default_session);
+ }
+ g_message("Initial session set to: %s", focus_ring_get_value(session_ring));
+
+ app->session_ring = session_ring;
+}
+/* Retrieves the `key` field of a session, used to pull current session out of
+ * a FocusRing.
+ */
+static gchar *get_session_key(gconstpointer data)
+{
+ LightDMSession *session = (LightDMSession *) data;
+ return (gchar *) lightdm_session_get_key(session);
+}
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..6351e7c
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,14 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <lightdm.h>
+
+#include "app.h"
+
+
+void connect_to_lightdm_daemon(LightDMGreeter *greeter);
+void make_session_focus_ring(App *app);
+void begin_authentication_as_default_user(App *app);
+void remove_char(char *str, char garbage);
+
+#endif