1
0
Fork 0

Adding upstream version 48.1.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-22 23:03:09 +02:00
parent 52a58131b2
commit 4d6cf1c280
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
250 changed files with 93774 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
# Editor temporary files
*~
*.swp
# GNOME Builder litter
/.buildconfig
# Subprojects for building a GTK 4 development snapshot
/subprojects/graphene.wrap
/subprojects/graphene
/subprojects/gstreamer-full.wrap
/subprojects/gtk4/
/subprojects/libsass.wrap
/subprojects/libsass/
/subprojects/sassc.wrap
/subprojects/sassc/

113
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,113 @@
include:
- remote: 'https://gitlab.gnome.org/Infrastructure/freedesktop-ci-templates/-/raw/593a0a5fe35a523a646a7efae5471c9759b8fba3/templates/fedora.yml'
- component: gitlab.gnome.org/GNOME/citemplates/release-service@master
inputs:
job-stage: "deploy"
dist-job-name: "build-maximal"
tarball-artifact-path: "_build/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz"
variables:
FDO_UPSTREAM_REPO: gnome/gnome-initial-setup
stages:
- prepare
- build
- deploy
.fedora.container.common:
variables:
# When branching a stable release, change 'main'
# to the release number/branch to ensure that
# a new image will be created, tailored for the
# stable branch.
FDO_DISTRIBUTION_TAG: '2025-01-31.01-main'
FDO_DISTRIBUTION_VERSION: 42
# Workaround for https://gitlab.gnome.org/Infrastructure/Infrastructure/-/issues/1247
FDO_DISTRIBUTION_EXEC: |
rm -r /var/lib/gdm/.config
# See also https://gitlab.gnome.org/Infrastructure/freedesktop-ci-templates
build.container.fedora@x86_64:
extends:
- '.fdo.container-build@fedora'
- '.fedora.container.common'
stage: 'prepare'
variables:
# no need to pull the whole tree for rebuilding the image
GIT_STRATEGY: none
# Expiry sets fdo.expires on the image
FDO_EXPIRES_AFTER: 8w
FDO_DISTRIBUTION_PACKAGES: >-
@c-development
accountsservice-devel
ccache
desktop-file-utils
fontconfig-devel
gdm-devel
geoclue2-devel
geocode-glib-devel
git
glib2-devel
gnome-desktop4-devel
gsettings-desktop-schemas-devel
gtk4-devel
ibus-devel
krb5-devel
libgweather4-devel
libadwaita-devel
libnma-gtk4-devel
libpwquality-devel
libsecret-devel
malcontent-ui-devel
meson
polkit-devel
webkitgtk6.0-devel
.job_template: &job_definition
extends:
- '.fdo.distribution-image@fedora'
- '.fedora.container.common'
stage: build
script:
- git config --global --add safe.directory $CI_PROJECT_DIR
# In general, we would like warnings to be fatal. However, code copied from
# gnome-control-center uses many deprecated functions. Until we have a good
# answer to sharing that code (#68), make those warnings non-fatal.
- meson setup
--wrap-mode=nofallback
--fatal-meson-warnings
-Dsystemd=${EXPLICIT_FEATURES}
--auto-features ${AUTO_FEATURES}
${OPTIONS}
-Dwerror=true -Dc_args=-Wno-error=deprecated-declarations
${EXTRA_PARAMETERS}
_build
.
- cd _build
- ninja -v
# Check that strings can be extracted
- ninja -v gnome-initial-setup-pot
- meson dist
artifacts:
when: always
name: "${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}"
paths:
- "${CI_PROJECT_DIR}/_build/meson-logs"
- "${CI_PROJECT_DIR}/_build/meson-dist/*.tar.xz"
build-minimal:
<<: *job_definition
variables:
EXPLICIT_FEATURES: 'false'
AUTO_FEATURES: 'disabled'
OPTIONS: ''
build-maximal:
<<: *job_definition
variables:
EXPLICIT_FEATURES: 'true'
AUTO_FEATURES: 'enabled'
OPTIONS: '-Dvendor-conf-file=/var/lib/weird-vendor-specific-path.ini'

174
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,174 @@
Initial Setup
=============
The first time a new system is booted is a special situation. There is
no user account yet, and a few basic setup steps need to be performed
before it can be considered fully usable. The initial setup mode is an
attempt to solve these problems.
When in initial setup mode, GDM does not bring up the regular greeter
for the login screen, but instead starts the `gnome-initial-setup`
application in a special session. `gnome-initial-setup` offers a series
of steps to:
1. Select a language
2. Select a keyboard layout
3. Connect to the network
4. Adjust some privacy settings
5. Set the right location/timezone
6. Configure software sources
7. Create a new user account
8. Configure parental controls
In terms of the user experience, we want the initial setup to seamlessly
switch to the regular user session. In particular, we don't want to
make the user enter their credentials again on the login screen.
We can't run the `gnome-initial-setup` application with the correct user,
since the user account does not exist yet at that time. Therefore, GDM
runs `gnome-initial-setup` as a `gnome-initial-setup` use. When
`gnome-inital-setup` is done, it then initiates an autologin for the newly
created user account to switch to the real session.
Due to this arrangement, we need to copy all the settings that have been
changed during the initial setup session from the `gnome-initial-setup`
user to the real user.
Mechanics
=========
By default, this functionality is enabled in GDM. To disable it, add the
following to GDM's configuration file:
```ini
[daemon]
InitialSetupEnable=False
```
(By default, this file lives at `/etc/gdm/custom.conf`, but this can be
customized at GDM build time. For Debian-like systems, use
`/etc/gdm3/daemon.conf`.)
If enabled, GDM will trigger Initial Setup only if there are no users configured
yet and some other conditions also hold. You can force GDM to run Initial Setup
even if users exist by appending `gnome.initial-setup=1` to the kernel command line.
The session that gdm starts for the initial-setup session is
defined by the file
`/usr/share/gnome-session/sessions/gnome-initial-setup.session`.
Like the regular greeter session, it uses the desktop files in
`/usr/share/gdm/greeter/applications/`.
`gnome-initial-setup` also has an "existing user" mode which activates
`gnome-initial-setup` when a user first logs in. The
`gnome-initial-setup-first-login.desktop` in the
[xdg autostart][] directory uses `gnome-session` to check if the user has a
`gnome-initial-setup-done` file in their `XDG_CONFIG_DIR`; if they don't,
`gnome-initial-setup` will launch with pages that are suitable for a
non-privileged user and on exiting will write the done file. However, since
GNOME 40, this mode would interfere with the first-login tour
prompt, so `gnome-initial-setup` silently writes the stamp file and exits.
[xdg autostart]: https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html
"The Desktop Application Autostart Specification"
Developing
==========
Recommendations
----------------
- Build in a container, using a tool like `toolbox`. See the
[Toolbox documentation](https://docs.fedoraproject.org/en-US/fedora-silverblue/toolbox/)
for more details.
- [GNOME Builder](https://flathub.org/apps/org.gnome.Builder) is an IDE for
GNOME with integrated support for Toolbox.
- [Builder] is an IDE for GNOME
Dependencies
------------
If you are building on a Fedora system or container, you can typically get
new-enough dependencies with:
```bash
sudo dnf builddep gnome-initial-setup
```
On Debian-based distros or containers:
```bash
sudo apt build-dep gnome-initial-setup
```
Building with GNOME Builder
---------------------------
Builder should be able to build & run Initial Setup automatically. However you
may like to disable the installation of systemd units and sysuser.d snippets by
opening the build settings (`Alt` + `,`), selecting the current configuration,
and adding `-Dsystemd=false` under *Configure Options*.
Building by hand
----------------
Inside the `gnome-initial-setup` directory, run:
```bash
meson setup _build
```
To build:
```bash
cd _build
ninja
```
And to run:
```bash
UNDER_JHBUILD=1 ./gnome-initial-setup/gnome-initial-setup
```
Mock mode
---------
Set the `UNDER_JHBUILD` environment variable when running Initial Setup to
enable “Mock mode”. In this mode, most changes will not be saved to disk.
FAQ
===
Why does the `welcome` not appear
---------------------------------
The `welcome` page is only shown when the `language` page is skipped. You can
either create a suitable
[vendor configuration](./README.md#vendor-configuration) file at
`$(sysconfdir)/gnome-initial-setup/vendor.conf` or
`$(datadir)/gnome-initial-setup/vendor.conf`:
```ini
[pages]
skip=language
```
Or you can comment out the following lines in
`gnome-initial-setup/gnome-initial-setup.c`:
```c
/* if (strcmp (page_id, "welcome") == 0)
return !should_skip_page ("language", skip_pages); */
```
Enterprise Login
----------------
Initial Setup can configure the system to be part of an enterprise domain.
This functionality is available if `realmd` is installed (or, more precisely,
the name `org.freedesktop.realmd` is owned on the system bus) and hidden if not.
The FreeIPA project runs a [demo server](https://www.freeipa.org/page/Demo),
which may be useful to test this functionality if you do not have an
enterprise domain of your own to test against.

339
COPYING Normal file
View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) 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
this service 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 make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. 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.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE 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.
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
convey 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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision 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, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This 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.

36
DEBUGGING.md Normal file
View file

@ -0,0 +1,36 @@
Debugging Initial Setup can be tricky if what you need to work on relies on the
system booting for the very first time, and can't be reproduced easily (or at
all) from a regular session once the “real” initial setup has happened and the
first user has been created.
Examples of things you might want to do that can be especially tricky if you
need to do them before creating the very first user:
- Changing the default configuration of the system
- Installing/updating packages to try something out
- Getting logs from the journal
- Getting a backtrace to debug a crash happening
Here are some strategies that may be useful.
## Force GDM to launch Initial Setup
GDM is responsible for launching Initial Setup in a cut-down desktop session
when no users exist on the system. Since GDM 3.26.1, you can force it to launch
it even if users already exist by adding ` gnome.initial-setup=1` to the kernel
command line. The exact method depends on your distribution, but in general terms:
- Restart the computer
- Force GRUB to display a boot menu (distro-dependent; try hitting `Escape` or
holding `Shift` during boot)
- Hit `e` to edit the default menu entry
- Use the arrow keys to select the line beginning with `linux` or `linuxefi`
- Hit `End`
- Type ` gnome.initial-setup=1`
- Hit `F10` to boot
## Get a root shell before Initial Setup is complete
Follow the steps above, but add ` systemd.debug_shell` to the end of the kernel
command line. Hit `Ctrl + Alt + F9` to switch to VT9, where you should find a
root shell awaiting you.

2464
NEWS Normal file

File diff suppressed because it is too large Load diff

116
README.md Normal file
View file

@ -0,0 +1,116 @@
GNOME Initial Setup
===================
After acquiring or installing a new system there are a few essential things
to set up before use. Initial Setup aims to provide a simple, easy, and safe way
to prepare a new system. This should only include a few essential steps for
which we can't provide good defaults. The desired experience is that the system
boots straight into Initial Setup, and when the setup tasks are completed, we
smoothly transition into the user session for the newly-created user account.
There are two modes.
New User Mode
-------------
When there are no existing user accounts on the system, gdm launches Initial
Setup in a special initial setup session that runs GNOME Shell with a somewhat
reduced UI, similar to the way it is used on the login screen. In this mode,
Initial Setup will create a new user account. By default, all pages except
Welcome are displayed:
* Language
* Keyboard
* Network
* Privacy
* Timezone
* Software (currently only used by Fedora)
* Account
* Password
* Parental Controls (if malcontent is enabled)
* Parent Password (if malcontent is enabled)
* Summary
There are some deficiencies with this mode. First, some pages are redundant
with distro installers. Linux distros do not want to prompt the user to
configure the same thing multiple times. Distros have to suppress particular
pages using the vendor.conf file (discussed below). For example, Fedora's
vendor.conf suppresses the Language and Keyboard pages when in new user mode,
and suppresses the Timezone page always, to avoid redundancy with its Anaconda
installer. The Welcome page is displayed whenever the Language page is
suppressed via vendor.conf. Second, the new user mode will be used for both
regular and OEM installs, because there is no separate OEM mode. When pages are
suppressed to avoid redundancy with distro installers, OEM installs suffer
because there users never run the installer and therefore never receive the
suppressed pages. In the future, we should create a separate OEM mode to fix
this.
Existing User Mode
------------------
Initial Setup has code to support running when logging into a new user account
for the first time. Confusingly, this is called existing user mode, but it
makes sense if you think "the user already exists and Initial Setup does not
need to create it." Although the code exists, it is actually impossible to ever
access existing user mode as of Initial Setup 40. This mode was entirely
disabled in https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/merge_requests/113
to avoid conflicting with GNOME Tour. It would be nice to bring it back, but to
do so, we would want to fix https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/12.
Historically, existing user mode runs in the normal user account session, but we
really need it to run in the same special initial setup session that is used for
new user mode. Otherwise, the Language page does not actually work; the locale
has to be set before launching the normal user session, and cannot be changed
once the session has started. Since the code still exists, it is worth
documenting here even though it is currently unreachable.
When running in existing user mode, the Timezone, Software, Account, Password,
Parental Controls, and Parent Password pages are all disabled because they do not
make sense in this mode. This results in the following workflow:
* Language
* Keyboard
* Network
* Privacy
* Summary
Although this mode is unreachable in the upstream version of Initial Setup, both
Debian and Ubuntu have downstream patches to restore it.
Vendor Configuration
--------------------
Some aspects of Initial Setup's behaviour can be overridden through a
_vendor configuration file_.
By default, Initial Setup will try to read configuration from
`$(sysconfdir)/gnome-initial-setup/vendor.conf` (i.e.
`/etc/gnome-initial-setup/vendor.conf` in a typical installation). If this file
does not exist or cannot be read, Initial Setup will read
`$(datadir)/gnome-initial-setup/vendor.conf` (i.e.
`/usr/share/gnome-initial-setup/vendor.conf`). The intention is that
distributions will provide their configuration (if any) in the latter file,
with the former used by administrators or hardware vendors to override the
distribution's configuration.
For backwards-compatibility, a `vendor-conf-file` option can be passed to
`meson configure`. If specified, Initial Setup will *only* try to read
configuration from that path; neither of the default paths will be checked.
Here's a (contrived) example of what can be controlled using this file:
```ini
[pages]
# Never show the timezone page
skip=timezone
# Don't show the language and keyboard pages in the 'first boot' situation,
# only when running for an existing user
existing_user_only=language;keyboard
# Only show the privacy page in the 'first boot' situation
new_user_only=privacy
```
License
-------
GNOME Initial Setup is distributed under the terms of the GNU General Public License,
version 2 or later. See the [COPYING](COPYING) file for details.

View file

@ -0,0 +1,29 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
// DO NOT EDIT THIS FILE, it will be overwritten on update.
//
// Allow the gnome-initial-setup user to do certain actions without
// being interrupted by password dialogs
polkit.addRule(function(action, subject) {
if (subject.user !== 'gnome-initial-setup')
return undefined;
var actionMatches = (action.id.indexOf('org.freedesktop.hostname1.') === 0 ||
action.id.indexOf('org.freedesktop.NetworkManager.') === 0 ||
action.id.indexOf('org.freedesktop.locale1.') === 0 ||
action.id.indexOf('org.freedesktop.accounts.') === 0 ||
action.id.indexOf('org.freedesktop.timedate1.') === 0 ||
action.id.indexOf('org.freedesktop.realmd.') === 0 ||
action.id.indexOf('com.endlessm.ParentalControls.') === 0 ||
action.id.indexOf('org.fedoraproject.thirdparty.') === 0);
if (actionMatches) {
if (subject.local)
return 'yes';
else
return 'auth_admin';
}
return undefined;
});

View file

@ -0,0 +1,14 @@
# This file is part of the gnome-initial-setup package and should not be changed.
#
# Instead create your own file next to it with a higher numbered prefix,
# and run
# dconf update
#
[org/gnome/desktop/a11y]
always-show-universal-access-status=true
[org/gnome/desktop/lockdown]
disable-lock-screen=true
disable-log-out=true
disable-user-switching=true

View file

@ -0,0 +1,3 @@
/org/gnome/desktop/lockdown/disable-lock-screen
/org/gnome/desktop/lockdown/disable-log-out
/org/gnome/desktop/lockdown/disable-user-switching

View file

@ -0,0 +1,2 @@
user-db:user
file-db:@DATADIR@/@PACKAGE@/initial-setup-dconf-defaults

25
data/dconf/meson.build Normal file
View file

@ -0,0 +1,25 @@
conf = configure_file(
input: 'gnome-initial-setup.in',
output: '@BASENAME@',
configuration: {
'DATADIR': prefix / get_option('datadir'),
'PACKAGE': meson.project_name(),
},
install_dir: get_option('datadir') / 'dconf' / 'profile',
)
dconf_defaults = custom_target('initial-setup-dconf-defaults',
output: 'initial-setup-dconf-defaults',
input: files(
'defaults/00-upstream-settings',
'defaults/locks/00-upstream-settings-locks',
),
command: [
find_program('dconf'),
'compile',
'@OUTPUT@',
meson.current_source_dir() / 'defaults',
],
install: true,
install_dir: get_option('datadir') / meson.project_name(),
)

View file

@ -0,0 +1,10 @@
[Desktop Entry]
Name=GNOME Initial Setup Copy Worker
Type=Application
Exec=@LIBEXECDIR@/gnome-initial-setup-copy-worker
OnlyShowIn=GNOME;
NoDisplay=true
X-GNOME-AutoRestart=false
X-GNOME-Autostart-Phase=EarlyInitialization
AutostartCondition=unless-exists gnome-initial-setup-done
X-GNOME-HiddenUnderSystemd=@systemd_hidden@

View file

@ -0,0 +1,18 @@
[Unit]
Description=GNOME Initial Setup Copy Worker
# Make sure we complete very early before most consumers are started.
Before=default.target graphical-session-pre.target
# Run before any systemd activated consumers.
Before=gnome-keyring-daemon.service
# Never run in GDM
ConditionUser=!@system
ConditionPathExists=!%E/gnome-initial-setup-done
[Service]
Type=oneshot
ExecStart=@libexecdir@/gnome-initial-setup-copy-worker
Restart=no

View file

@ -0,0 +1,13 @@
[Desktop Entry]
Name=Initial Setup
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
Icon=preferences-system
Exec=@LIBEXECDIR@/gnome-initial-setup --existing-user
Terminal=false
Type=Application
StartupNotify=true
Categories=GNOME;GTK;System;
OnlyShowIn=GNOME;
NoDisplay=true
AutostartCondition=unless-exists gnome-initial-setup-done
X-GNOME-HiddenUnderSystemd=@systemd_hidden@

View file

@ -0,0 +1,16 @@
[Unit]
Description=GNOME Initial Setup
BindsTo=gnome-session.target
After=gnome-session.target
# Never run in GDM
Conflicts=gnome-session@gnome-login.target
Conflicts=gnome-session@gnome-initial-setup.target
ConditionPathExists=!%E/gnome-initial-setup-done
[Service]
Type=oneshot
ExecStart=@libexecdir@/gnome-initial-setup --existing-user
Restart=no

View file

@ -0,0 +1,3 @@
# sysusers.d snippet for creating the gnome-inital-setup system user. See
# sysusers.d(5) for details.
u gnome-initial-setup - "GNOME Initial Setup" /run/gnome-initial-setup

View file

@ -0,0 +1,13 @@
[Desktop Entry]
Name=Initial Setup
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
Icon=preferences-system
Exec=@LIBEXECDIR@/gnome-initial-setup
Terminal=false
Type=Application
StartupNotify=true
Categories=GNOME;GTK;System;
OnlyShowIn=GNOME;
NoDisplay=true
X-GNOME-HiddenUnderSystemd=@systemd_hidden@
StartupWMClass=org.gnome.InitialSetup

View file

@ -0,0 +1,13 @@
[Unit]
Description=GNOME Initial Setup
RefuseManualStart=true
RefuseManualStop=true
BindsTo=gnome-session.target
After=gnome-session.target
[Service]
Type=simple
ExecStart=@libexecdir@/gnome-initial-setup
ExecStopPost=-@libexecdir@/gnome-session-ctl --shutdown
Restart=no

View file

@ -0,0 +1,8 @@
[Unit]
# Must be in sync with @this_component@.session
@wants_required_components@
Requires=@requires_component@.target
# Only difference to a standard GNOME session is the @this_component@ service
Requires=@this_component@.service

View file

@ -0,0 +1,4 @@
[GNOME Session]
Name=GNOME Initial Setup
# Must be in sync with gnome-session@@this_component@.target.d/session.conf drop-in
RequiredComponents=@gnome_session_required_components@;

8
data/initial-setup.json Normal file
View file

@ -0,0 +1,8 @@
{
"hasWindows": true,
"components": ["networkAgent"],
"panel": { "left": [],
"center": [],
"right": ["a11y", "keyboard", "quickSettings"]
}
}

30
data/meson-add-wants.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/sh
set -eu
# Script copied from systemd
unitdir="$1"
target="$2"
unit="$3"
case "$target" in
*/?*) # a path, but not just a slash at the end
dir="${DESTDIR:-}${target}"
;;
*)
dir="${DESTDIR:-}${unitdir}/${target}"
;;
esac
unitpath="${DESTDIR:-}${unitdir}/${unit}"
case "$target" in
*/)
mkdir -vp -m 0755 "$dir"
;;
*)
mkdir -vp -m 0755 "$(dirname "$dir")"
;;
esac
ln -vfs --relative "$unitpath" "$dir"

132
data/meson.build Normal file
View file

@ -0,0 +1,132 @@
subdir('dconf')
autostart_files = [
'gnome-initial-setup-copy-worker.desktop',
'gnome-initial-setup-first-login.desktop',
]
gis_shell_component = 'org.gnome.Shell'
gis_gnome_session_required_components = [
'org.gnome.SettingsDaemon.A11ySettings',
'org.gnome.SettingsDaemon.Color',
'org.gnome.SettingsDaemon.Datetime',
'org.gnome.SettingsDaemon.Housekeeping',
'org.gnome.SettingsDaemon.Keyboard',
'org.gnome.SettingsDaemon.MediaKeys',
'org.gnome.SettingsDaemon.Power',
'org.gnome.SettingsDaemon.PrintNotifications',
'org.gnome.SettingsDaemon.Rfkill',
'org.gnome.SettingsDaemon.ScreensaverProxy',
'org.gnome.SettingsDaemon.Sharing',
'org.gnome.SettingsDaemon.Smartcard',
'org.gnome.SettingsDaemon.Sound',
'org.gnome.SettingsDaemon.UsbProtection',
'org.gnome.SettingsDaemon.Wacom',
'org.gnome.SettingsDaemon.XSettings',
]
gis_user_session_wanted_components = gis_gnome_session_required_components
desktop_conf = configuration_data()
desktop_conf.set('LIBEXECDIR', libexec_dir)
desktop_conf.set('systemd_hidden', enable_systemd ? 'true' : 'false')
foreach desktop_file: autostart_files
i18n.merge_file(
input: configure_file(
input: files(desktop_file + '.in.in'),
output: desktop_file + '.in',
configuration: desktop_conf
),
output: desktop_file,
install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'autostart'),
po_dir: po_dir,
install: true,
type: 'desktop'
)
endforeach
i18n.merge_file(
input: configure_file(
input: files('gnome-initial-setup.desktop.in.in'),
output: 'gnome-initial-setup.desktop.in',
configuration: desktop_conf
),
output: 'gnome-initial-setup.desktop',
install_dir: join_paths(data_dir, 'applications'),
po_dir: po_dir,
install: true,
type: 'desktop'
)
data_conf = configuration_data()
data_conf.set('bindir', bin_dir)
data_conf.set('libexecdir', libexec_dir)
if enable_systemd
unit_files = {
'gnome-initial-setup-first-login.service' : [ 'gnome-session.target.wants/' ],
'gnome-initial-setup-copy-worker.service' : [ 'basic.target.wants/' ],
}
foreach unit, wants: unit_files
configure_file(
input: unit + '.in',
output: unit,
configuration: data_conf,
install_dir: systemd_userunitdir
)
foreach target: wants
meson.add_install_script('meson-add-wants.sh', systemd_userunitdir, target, unit)
endforeach
endforeach
gis_user_session_wanted_targets = []
foreach component: gis_user_session_wanted_components
gis_user_session_wanted_targets += 'Wants=@0@.target'.format(component)
endforeach
configure_file(
input: '@0@.session.conf.in'.format(meson.project_name()),
output: 'session.conf',
configuration: {
'this_component': meson.project_name(),
'requires_component': gis_shell_component,
'wants_required_components': '\n'.join(
gis_user_session_wanted_targets),
},
install_dir: systemd_userunitdir / 'gnome-session@@0@.target.d'.format(
meson.project_name()),
)
install_data('gnome-initial-setup.conf', install_dir: systemd_sysusersdir)
endif
rules_dir = join_paths(data_dir, 'polkit-1', 'rules.d')
configure_file(
input: '20-gnome-initial-setup.rules.in',
output: '20-gnome-initial-setup.rules',
install: true,
install_dir: rules_dir,
configuration: data_conf,
)
session_dir = join_paths(data_dir, 'gnome-session', 'sessions')
configure_file(
input: '@0@.session.in'.format(meson.project_name()),
output: '@BASENAME@',
configuration: {
'this_component': meson.project_name(),
'gnome_session_required_components': ';'.join([
gis_shell_component,
meson.project_name(),
] +
gis_gnome_session_required_components),
},
install_dir: session_dir,
)
mode_dir = join_paths(data_dir, 'gnome-shell', 'modes')
install_data('initial-setup.json', install_dir: mode_dir)

31
gnome-initial-setup.doap Normal file
View file

@ -0,0 +1,31 @@
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:gnome="http://api.gnome.org/doap-extensions#"
xmlns="http://usefulinc.com/ns/doap#">
<name xml:lang="en">GNOME Initial Setup</name>
<shortdesc xml:lang="en">Bootstrapping your OS</shortdesc>
<description xml:lang="en">gnome-initial-setup helps you set up your OS when you boot or log in for the first time.</description>
<download-page rdf:resource="http://download.gnome.org/sources/gnome-initial-setup/" />
<bug-database rdf:resource="https://gitlab.gnome.org/GNOME/gnome-initial-setup/issues/" />
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
<programming-language>C</programming-language>
<maintainer>
<foaf:Person>
<foaf:name>Matthias Clasen</foaf:name>
<foaf:mbox rdf:resource="mailto:mclasen@redhat.com" />
<gnome:userid>matthiasc</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
<foaf:Person>
<foaf:name>Will Thompson</foaf:name>
<foaf:mbox rdf:resource="mailto:wjt@gnome.org" />
<gnome:userid>wjt</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View file

@ -0,0 +1,315 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2 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/>.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <locale.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <fontconfig/fontconfig.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-languages.h>
#include "cc-common-language.h"
static char *get_lang_for_user_object_path (const char *path);
static char *current_language;
gboolean
cc_common_language_has_font (const gchar *locale)
{
const FcCharSet *charset;
FcPattern *pattern;
FcObjectSet *object_set;
FcFontSet *font_set;
gchar *language_code;
gboolean is_displayable;
is_displayable = FALSE;
pattern = NULL;
object_set = NULL;
font_set = NULL;
if (!gnome_parse_locale (locale, &language_code, NULL, NULL, NULL))
return FALSE;
charset = FcLangGetCharSet ((FcChar8 *) language_code);
if (!charset) {
/* fontconfig does not know about this language */
is_displayable = TRUE;
}
else {
/* see if any fonts support rendering it */
pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL);
if (pattern == NULL)
goto done;
object_set = FcObjectSetCreate ();
if (object_set == NULL)
goto done;
font_set = FcFontList (NULL, pattern, object_set);
if (font_set == NULL)
goto done;
is_displayable = (font_set->nfont > 0);
}
done:
if (font_set != NULL)
FcFontSetDestroy (font_set);
if (object_set != NULL)
FcObjectSetDestroy (object_set);
if (pattern != NULL)
FcPatternDestroy (pattern);
g_free (language_code);
return is_displayable;
}
gchar *
cc_common_language_get_current_language (void)
{
g_assert (current_language != NULL);
return g_strdup (current_language);
}
void
cc_common_language_set_current_language (const char *locale)
{
g_clear_pointer (&current_language, g_free);
current_language = gnome_normalize_locale (locale);
}
static gboolean
user_language_has_translations (const char *locale)
{
g_autofree char *name = NULL, *language_code = NULL, *territory_code = NULL;
gboolean ret;
if (!gnome_parse_locale (locale,
&language_code,
&territory_code,
NULL, NULL))
return FALSE;
name = g_strdup_printf ("%s%s%s",
language_code,
territory_code != NULL? "_" : "",
territory_code != NULL? territory_code : "");
ret = gnome_language_has_translations (name);
return ret;
}
static char *
get_lang_for_user_object_path (const char *path)
{
GError *error = NULL;
GDBusProxy *user;
GVariant *props;
char *lang;
user = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.Accounts",
path,
"org.freedesktop.Accounts.User",
NULL,
&error);
if (user == NULL) {
g_warning ("Failed to get proxy for user '%s': %s",
path, error->message);
g_error_free (error);
return NULL;
}
lang = NULL;
props = g_dbus_proxy_get_cached_property (user, "Language");
if (props != NULL) {
lang = g_variant_dup_string (props, NULL);
g_variant_unref (props);
}
g_object_unref (user);
return lang;
}
static void
add_other_users_language (GHashTable *ht)
{
GVariant *variant;
GVariantIter *vi;
GError *error = NULL;
const char *str;
GDBusProxy *proxy;
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.Accounts",
"/org/freedesktop/Accounts",
"org.freedesktop.Accounts",
NULL,
NULL);
if (proxy == NULL)
return;
variant = g_dbus_proxy_call_sync (proxy,
"ListCachedUsers",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if (variant == NULL) {
g_warning ("Failed to list existing users: %s", error->message);
g_error_free (error);
g_object_unref (proxy);
return;
}
g_variant_get (variant, "(ao)", &vi);
while (g_variant_iter_loop (vi, "o", &str)) {
char *lang;
char *name;
char *language;
lang = get_lang_for_user_object_path (str);
if (lang != NULL && *lang != '\0' &&
cc_common_language_has_font (lang) &&
user_language_has_translations (lang)) {
name = gnome_normalize_locale (lang);
if (!g_hash_table_lookup (ht, name)) {
language = gnome_get_language_from_locale (name, NULL);
g_hash_table_insert (ht, name, language);
}
else {
g_free (name);
}
}
g_free (lang);
}
g_variant_iter_free (vi);
g_variant_unref (variant);
g_object_unref (proxy);
}
/*
* Note that @lang needs to be formatted like the locale strings
* returned by gnome_get_all_locales().
*/
static void
insert_language (GHashTable *ht,
const char *lang)
{
locale_t locale;
char *label_own_lang;
char *label_current_lang;
char *label_untranslated;
char *key;
locale = newlocale (LC_ALL_MASK, lang, (locale_t) 0);
if (locale == (locale_t) 0) {
g_debug ("%s: Failed to create locale %s", G_STRFUNC, lang);
return;
}
freelocale (locale);
key = g_strdup (lang);
label_own_lang = gnome_get_language_from_locale (key, key);
label_current_lang = gnome_get_language_from_locale (key, current_language);
label_untranslated = gnome_get_language_from_locale (key, "C");
/* We don't have a translation for the label in
* its own language? */
if (label_own_lang == NULL || g_strcmp0 (label_own_lang, label_untranslated) == 0) {
if (g_strcmp0 (label_current_lang, label_untranslated) == 0)
g_hash_table_insert (ht, key, g_strdup (label_untranslated));
else
g_hash_table_insert (ht, key, g_strdup (label_current_lang));
} else {
g_hash_table_insert (ht, key, g_strdup (label_own_lang));
}
g_free (label_own_lang);
g_free (label_current_lang);
g_free (label_untranslated);
}
static void
insert_user_languages (GHashTable *ht)
{
g_autofree char *name = NULL;
/* Add the languages used by other users on the system */
add_other_users_language (ht);
/* Add current locale */
name = cc_common_language_get_current_language ();
if (g_hash_table_lookup (ht, name) == NULL) {
insert_language (ht, name);
}
}
GHashTable *
cc_common_language_get_initial_languages (void)
{
GHashTable *ht;
ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
insert_language (ht, "en_US.UTF-8");
#if 0
/* Having 9 languages in the list initially makes the window
* too high. With 8 languages, we end up exactly 768 pixels
* high. Sadly, that means we can't affort to show English
* twice.
*/
insert_language (ht, "en_GB.UTF-8");
#endif
insert_language (ht, "de_DE.UTF-8");
insert_language (ht, "fr_FR.UTF-8");
insert_language (ht, "es_ES.UTF-8");
insert_language (ht, "zh_CN.UTF-8");
insert_language (ht, "ja_JP.UTF-8");
insert_language (ht, "ru_RU.UTF-8");
insert_language (ht, "ar_EG.UTF-8");
insert_user_languages (ht);
return ht;
}

View file

@ -0,0 +1,37 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __CC_COMMON_LANGUAGE_H__
#define __CC_COMMON_LANGUAGE_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
gboolean cc_common_language_has_font (const gchar *locale);
void cc_common_language_set_current_language (const gchar *locale);
gchar *cc_common_language_get_current_language (void);
GHashTable *cc_common_language_get_initial_languages (void);
G_END_DECLS
#endif

View file

@ -0,0 +1,512 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* -*- encoding: utf8 -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include <glib-object.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "gis-assistant.h"
enum {
PROP_0,
PROP_TITLE,
PROP_LAST,
};
enum {
PAGE_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
static GParamSpec *obj_props[PROP_LAST];
struct _GisAssistant
{
GtkBox parent_instance;
GtkWidget *forward;
GtkWidget *accept;
GtkWidget *skip;
GtkWidget *back;
GtkWidget *cancel;
GtkWidget *spinner;
GtkWidget *titlebar;
GtkWidget *title;
GtkWidget *stack;
GList *pages;
GisPage *current_page;
};
G_DEFINE_TYPE (GisAssistant, gis_assistant, GTK_TYPE_BOX)
static void
visible_child_changed (GisAssistant *assistant)
{
g_signal_emit (assistant, signals[PAGE_CHANGED], 0);
}
static void
switch_to (GisAssistant *assistant,
GisPage *page)
{
g_return_if_fail (page != NULL);
gtk_stack_set_visible_child (GTK_STACK (assistant->stack), GTK_WIDGET (page));
}
static inline gboolean
should_show_page (GisPage *page)
{
return gtk_widget_get_visible (GTK_WIDGET (page));
}
static GisPage *
find_next_page (GisAssistant *self,
GisPage *page)
{
GList *l = g_list_find (self->pages, page);
g_return_val_if_fail (l != NULL, NULL);
/* We need the next page */
l = l->next;
for (; l != NULL; l = l->next)
{
GisPage *page = GIS_PAGE (l->data);
if (should_show_page (page))
return page;
}
return NULL;
}
static void
switch_to_next_page (GisAssistant *assistant)
{
switch_to (assistant, find_next_page (assistant, assistant->current_page));
}
static void
on_apply_done (GisPage *page,
gboolean valid,
gpointer user_data)
{
GisAssistant *assistant = GIS_ASSISTANT (user_data);
if (valid)
switch_to_next_page (assistant);
}
void
gis_assistant_next_page (GisAssistant *assistant)
{
if (assistant->current_page)
gis_page_apply_begin (assistant->current_page, on_apply_done, assistant);
else
switch_to_next_page (assistant);
}
static GisPage *
find_prev_page (GisAssistant *self,
GisPage *page)
{
GList *l = g_list_find (self->pages, page);
g_return_val_if_fail (l != NULL, NULL);
/* We need the previous page */
l = l->prev;
for (; l != NULL; l = l->prev)
{
GisPage *page = GIS_PAGE (l->data);
if (should_show_page (page))
return page;
}
return NULL;
}
void
gis_assistant_previous_page (GisAssistant *assistant)
{
g_return_if_fail (assistant->current_page != NULL);
switch_to (assistant, find_prev_page (assistant, assistant->current_page));
}
static void
set_navigation_button (GisAssistant *assistant,
GtkWidget *widget,
gboolean sensitive)
{
gtk_widget_set_visible (assistant->forward, (widget == assistant->forward));
gtk_widget_set_sensitive (assistant->forward, (widget == assistant->forward && sensitive));
gtk_widget_set_visible (assistant->accept, (widget == assistant->accept));
gtk_widget_set_sensitive (assistant->accept, (widget == assistant->accept && sensitive));
gtk_widget_set_visible (assistant->skip, (widget == assistant->skip));
gtk_widget_set_sensitive (assistant->skip, (widget == assistant->skip && sensitive));
}
void
update_navigation_buttons (GisAssistant *assistant)
{
GisPage *page = assistant->current_page;
GList *l;
gboolean is_last_page;
if (page == NULL)
return;
l = g_list_find (assistant->pages, page);
is_last_page = (l->next == NULL);
if (is_last_page)
{
gtk_widget_set_visible (assistant->back, FALSE);
gtk_widget_set_visible (assistant->forward, FALSE);
gtk_widget_set_visible (assistant->skip, FALSE);
gtk_widget_set_visible (assistant->cancel, FALSE);
gtk_widget_set_visible (assistant->accept, FALSE);
}
else
{
gboolean is_first_page;
GtkWidget *next_widget;
is_first_page = (l->prev == NULL);
gtk_widget_set_visible (assistant->back, !is_first_page);
if (gis_page_get_needs_accept (page))
next_widget = assistant->accept;
else
next_widget = assistant->forward;
if (gis_page_get_complete (page)) {
set_navigation_button (assistant, next_widget, TRUE);
} else if (gis_page_get_skippable (page)) {
set_navigation_button (assistant, assistant->skip, TRUE);
} else {
set_navigation_button (assistant, next_widget, FALSE);
}
/* This really means "page manages its own forward button" */
if (gis_page_get_has_forward (page)) {
GtkWidget *dummy_widget = NULL;
/* Ensure none of the three buttons is visible or sensitive */
set_navigation_button (assistant, dummy_widget, FALSE);
}
}
}
static void
update_applying_state (GisAssistant *assistant)
{
gboolean applying = FALSE;
gboolean is_first_page = FALSE;
if (assistant->current_page)
{
applying = gis_page_get_applying (assistant->current_page);
is_first_page = assistant->pages->data == assistant->current_page;
}
gtk_widget_set_sensitive (assistant->forward, !applying);
gtk_widget_set_visible (assistant->back, !applying && !is_first_page);
gtk_widget_set_visible (assistant->cancel, applying);
gtk_widget_set_visible (assistant->spinner, applying);
if (applying)
gtk_spinner_start (GTK_SPINNER (assistant->spinner));
else
gtk_spinner_stop (GTK_SPINNER (assistant->spinner));
}
static void
update_titlebar (GisAssistant *assistant)
{
gtk_label_set_label (GTK_LABEL (assistant->title),
gis_assistant_get_title (assistant));
}
static void
page_notify (GisPage *page,
GParamSpec *pspec,
GisAssistant *assistant)
{
if (page != assistant->current_page)
return;
if (strcmp (pspec->name, "title") == 0)
{
g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]);
update_titlebar (assistant);
}
else if (strcmp (pspec->name, "applying") == 0)
{
update_applying_state (assistant);
}
else
{
update_navigation_buttons (assistant);
}
}
void
gis_assistant_add_page (GisAssistant *assistant,
GisPage *page)
{
GList *link;
/* Page shouldn't already exist */
g_return_if_fail (!g_list_find (assistant->pages, page));
assistant->pages = g_list_append (assistant->pages, page);
link = g_list_last (assistant->pages);
link = link->prev;
g_signal_connect (page, "notify", G_CALLBACK (page_notify), assistant);
gtk_stack_add_child (GTK_STACK (assistant->stack), GTK_WIDGET (page));
/* Update buttons if current page is now the second last page */
if (assistant->current_page && link &&
link->data == assistant->current_page)
update_navigation_buttons (assistant);
}
void
gis_assistant_remove_page (GisAssistant *assistant,
GisPage *page)
{
assistant->pages = g_list_remove (assistant->pages, page);
if (page == assistant->current_page)
assistant->current_page = NULL;
gtk_stack_remove (GTK_STACK (assistant->stack), GTK_WIDGET (page));
}
GisPage *
gis_assistant_get_current_page (GisAssistant *assistant)
{
return assistant->current_page;
}
GList *
gis_assistant_get_all_pages (GisAssistant *assistant)
{
return assistant->pages;
}
static void
go_forward (GtkWidget *button,
GisAssistant *assistant)
{
gis_assistant_next_page (assistant);
}
static void
go_backward (GtkWidget *button,
GisAssistant *assistant)
{
gis_assistant_previous_page (assistant);
}
static void
do_cancel (GtkWidget *button,
GisAssistant *assistant)
{
if (assistant->current_page)
gis_page_apply_cancel (assistant->current_page);
}
const gchar *
gis_assistant_get_title (GisAssistant *assistant)
{
if (assistant->current_page != NULL)
return gis_page_get_title (assistant->current_page);
else
return "";
}
GtkWidget *
gis_assistant_get_titlebar (GisAssistant *assistant)
{
return assistant->titlebar;
}
static void
update_current_page (GisAssistant *assistant,
GisPage *page)
{
if (assistant->current_page == page)
return;
assistant->current_page = page;
g_object_notify_by_pspec (G_OBJECT (assistant), obj_props[PROP_TITLE]);
update_titlebar (assistant);
update_applying_state (assistant);
update_navigation_buttons (assistant);
gtk_widget_grab_focus (assistant->forward);
if (page)
gis_page_shown (page);
}
static void
current_page_changed (GObject *gobject,
GParamSpec *pspec,
gpointer user_data)
{
GisAssistant *assistant = GIS_ASSISTANT (user_data);
GtkStack *stack = GTK_STACK (gobject);
GtkWidget *new_page = gtk_stack_get_visible_child (stack);
update_current_page (assistant, GIS_PAGE (new_page));
}
void
gis_assistant_locale_changed (GisAssistant *assistant)
{
GList *l;
gtk_button_set_label (GTK_BUTTON (assistant->forward), _("_Next"));
gtk_button_set_label (GTK_BUTTON (assistant->accept), _("_Accept"));
gtk_button_set_label (GTK_BUTTON (assistant->skip), _("_Skip"));
gtk_button_set_label (GTK_BUTTON (assistant->back), _("_Previous"));
gtk_button_set_label (GTK_BUTTON (assistant->cancel), _("_Cancel"));
for (l = assistant->pages; l != NULL; l = l->next)
gis_page_locale_changed (l->data);
update_titlebar (assistant);
}
gboolean
gis_assistant_save_data (GisAssistant *assistant,
GError **error)
{
GList *l;
for (l = assistant->pages; l != NULL; l = l->next)
{
if (!gis_page_save_data (l->data, error))
return FALSE;
}
return TRUE;
}
static void
gis_assistant_init (GisAssistant *assistant)
{
gtk_widget_init_template (GTK_WIDGET (assistant));
g_signal_connect (assistant->stack, "notify::visible-child",
G_CALLBACK (current_page_changed), assistant);
g_signal_connect (assistant->forward, "clicked", G_CALLBACK (go_forward), assistant);
g_signal_connect (assistant->accept, "clicked", G_CALLBACK (go_forward), assistant);
g_signal_connect (assistant->skip, "clicked", G_CALLBACK (go_forward), assistant);
g_signal_connect (assistant->back, "clicked", G_CALLBACK (go_backward), assistant);
g_signal_connect (assistant->cancel, "clicked", G_CALLBACK (do_cancel), assistant);
gis_assistant_locale_changed (assistant);
update_applying_state (assistant);
}
static void
gis_assistant_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GisAssistant *assistant = GIS_ASSISTANT (object);
switch (prop_id)
{
case PROP_TITLE:
g_value_set_string (value, gis_assistant_get_title (assistant));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gis_assistant_class_init (GisAssistantClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-assistant.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, forward);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, accept);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, skip);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, back);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, cancel);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, spinner);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, titlebar);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, title);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAssistant, stack);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), visible_child_changed);
gobject_class->get_property = gis_assistant_get_property;
obj_props[PROP_TITLE] =
g_param_spec_string ("title",
"", "",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
/**
* GisAssistant::page-changed:
* @assistant: the #GisAssistant
*
* The ::page-changed signal is emitted when the visible page
* changed.
*/
signals[PAGE_CHANGED] =
g_signal_new ("page-changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/initial-setup">
<file preprocess="xml-stripblanks">gis-assistant.ui</file>
<file preprocess="xml-stripblanks">gis-page-header.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,48 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#pragma once
#include "gis-page.h"
G_BEGIN_DECLS
#define GIS_TYPE_ASSISTANT (gis_assistant_get_type ())
G_DECLARE_FINAL_TYPE (GisAssistant, gis_assistant, GIS, ASSISTANT, GtkBox)
void gis_assistant_add_page (GisAssistant *assistant,
GisPage *page);
void gis_assistant_remove_page (GisAssistant *assistant,
GisPage *page);
void gis_assistant_next_page (GisAssistant *assistant);
void gis_assistant_previous_page (GisAssistant *assistant);
GisPage * gis_assistant_get_current_page (GisAssistant *assistant);
GList * gis_assistant_get_all_pages (GisAssistant *assistant);
const gchar *gis_assistant_get_title (GisAssistant *assistant);
GtkWidget *gis_assistant_get_titlebar (GisAssistant *assistant);
void gis_assistant_locale_changed (GisAssistant *assistant);
gboolean gis_assistant_save_data (GisAssistant *assistant,
GError **error);
G_END_DECLS

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk30">
<object class="GtkHeaderBar" id="titlebar">
<property name="show-title-buttons">False</property>
<child type="title">
<object class="GtkLabel" id="title">
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkButton" id="cancel">
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="back">
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="placeholder">
</object>
</child>
<child type="end">
<object class="GtkSpinner" id="spinner" />
</child>
<child type="end">
<object class="GtkButton" id="skip">
<property name="visible">False</property>
<property name="use-underline">True</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="forward">
<property name="visible">False</property>
<property name="use-underline">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child type="end">
<object class="GtkButton" id="accept">
<property name="visible">False</property>
<property name="use-underline">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
<object class="GtkSizeGroup" id="headerheight">
<property name="mode">vertical</property>
<widgets>
<widget name="title"/>
<widget name="placeholder"/>
</widgets>
</object>
<template class="GisAssistant" parent="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="stack">
<property name="transition-type">slide-left-right</property>
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="hhomogeneous">False</property>
<property name="vhomogeneous">False</property>
<signal name="notify::visible-child" handler="visible_child_changed" object="GisAssistant" swapped="yes"/>
</object>
</child>
</template>
</interface>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,132 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GIS_DRIVER_H__
#define __GIS_DRIVER_H__
#include "gis-assistant.h"
#include "gis-page.h"
#include <act/act-user-manager.h>
#include <gdm/gdm-client.h>
#include <adwaita.h>
G_BEGIN_DECLS
#define GIS_TYPE_DRIVER (gis_driver_get_type ())
G_DECLARE_FINAL_TYPE (GisDriver, gis_driver, GIS, DRIVER, AdwApplication)
typedef enum {
UM_LOCAL,
UM_ENTERPRISE,
NUM_MODES,
} UmAccountMode;
typedef enum {
GIS_DRIVER_MODE_NEW_USER,
GIS_DRIVER_MODE_EXISTING_USER,
} GisDriverMode;
GisAssistant *gis_driver_get_assistant (GisDriver *driver);
void gis_driver_set_user_permissions (GisDriver *driver,
ActUser *user,
const gchar *password);
void gis_driver_get_user_permissions (GisDriver *driver,
ActUser **user,
const gchar **password);
void gis_driver_set_parent_permissions (GisDriver *driver,
ActUser *parent,
const gchar *password);
void gis_driver_get_parent_permissions (GisDriver *driver,
ActUser **parent,
const gchar **password);
void gis_driver_set_account_mode (GisDriver *driver,
UmAccountMode mode);
UmAccountMode gis_driver_get_account_mode (GisDriver *driver);
void gis_driver_set_parental_controls_enabled (GisDriver *driver,
gboolean parental_controls_enabled);
gboolean gis_driver_get_parental_controls_enabled (GisDriver *driver);
void gis_driver_set_user_language (GisDriver *driver,
const gchar *lang_id,
gboolean update_locale);
const gchar *gis_driver_get_user_language (GisDriver *driver);
void gis_driver_set_username (GisDriver *driver,
const gchar *username);
const gchar *gis_driver_get_username (GisDriver *driver);
void gis_driver_set_full_name (GisDriver *driver,
const gchar *full_name);
const gchar *gis_driver_get_full_name (GisDriver *driver);
void gis_driver_set_avatar (GisDriver *driver,
GdkTexture *avatar);
GdkTexture *gis_driver_get_avatar (GisDriver *driver);
void gis_driver_set_has_default_avatar (GisDriver *driver,
gboolean has_default_avatar);
gboolean gis_driver_get_has_default_avatar (GisDriver *driver);
gboolean gis_driver_get_gdm_objects (GisDriver *driver,
GdmGreeter **greeter,
GdmUserVerifier **user_verifier);
GisDriverMode gis_driver_get_mode (GisDriver *driver);
gboolean gis_driver_is_small_screen (GisDriver *driver);
void gis_driver_add_page (GisDriver *driver,
GisPage *page);
void gis_driver_hide_window (GisDriver *driver);
gboolean gis_driver_save_data (GisDriver *driver,
GError **error);
gboolean gis_driver_conf_get_boolean (GisDriver *driver,
const gchar *group,
const gchar *key,
gboolean default_value);
GStrv gis_driver_conf_get_string_list (GisDriver *driver,
const gchar *group,
const gchar *key,
gsize *out_length);
gchar *gis_driver_conf_get_string (GisDriver *driver,
const gchar *group,
const gchar *key);
GisDriver *gis_driver_new (GisDriverMode mode);
G_END_DECLS
#endif /* __GIS_DRIVER_H__ */

View file

@ -0,0 +1,104 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2014 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <string.h>
#include <gio/gio.h>
#include "gis-keyring.h"
#include <libsecret/secret.h>
#define DUMMY_PWD "gis"
/* We never want to see a keyring dialog, but we need to make
* sure a keyring is present.
*
* To achieve this, install a prompter for gnome-keyring that
* never shows any UI, and create a keyring, if one does not
* exist yet.
*/
void
gis_ensure_login_keyring ()
{
g_autoptr(GSubprocess) subprocess = NULL;
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GError) error = NULL;
g_debug ("launching gnome-keyring-daemon --unlock");
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE);
subprocess = g_subprocess_launcher_spawn (launcher, &error, "gnome-keyring-daemon", "--unlock", NULL);
if (subprocess == NULL) {
g_warning ("Failed to spawn gnome-keyring-daemon --unlock: %s", error->message);
return;
}
if (!g_subprocess_communicate_utf8 (subprocess, DUMMY_PWD, NULL, NULL, NULL, &error)) {
g_warning ("Failed to communicate with gnome-keyring-daemon: %s", error->message);
return;
}
}
void
gis_update_login_keyring_password (const gchar *new_)
{
g_autoptr(GDBusConnection) bus = NULL;
g_autoptr(SecretService) service = NULL;
g_autoptr(SecretValue) old_secret = NULL;
g_autoptr(SecretValue) new_secret = NULL;
g_autoptr(GError) error = NULL;
service = secret_service_get_sync (SECRET_SERVICE_OPEN_SESSION, NULL, &error);
if (service == NULL) {
g_warning ("Failed to get secret service: %s", error->message);
return;
}
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (bus == NULL) {
g_warning ("Failed to get session bus: %s", error->message);
return;
}
old_secret = secret_value_new (DUMMY_PWD, strlen (DUMMY_PWD), "text/plain");
new_secret = secret_value_new (new_, strlen (new_), "text/plain");
g_dbus_connection_call_sync (bus,
"org.gnome.keyring",
"/org/freedesktop/secrets",
"org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface",
"ChangeWithMasterPassword",
g_variant_new ("(o@(oayays)@(oayays))",
"/org/freedesktop/secrets/collection/login",
secret_service_encode_dbus_secret (service, old_secret),
secret_service_encode_dbus_secret (service, new_secret)),
NULL,
0,
G_MAXINT,
NULL, &error);
if (error != NULL) {
g_warning ("Failed to change keyring password: %s", error->message);
}
}

View file

@ -0,0 +1,35 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2014 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GIS_KEYRING_H__
#define __GIS_KEYRING_H__
#include <glib-object.h>
G_BEGIN_DECLS
void gis_ensure_login_keyring ();
void gis_update_login_keyring_password (const gchar *new_);
G_END_DECLS
#endif /* __GIS_KEYRING_H__ */

View file

@ -0,0 +1,199 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* -*- encoding: utf8 -*- */
/*
* Copyright (C) 2019 Purism SPC
*
* 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 2 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/>.
*
* Written by:
* Adrien Plazas <kekun.plazas@laposte.net>
*/
#include "config.h"
#include <glib/gi18n.h>
#include "gis-page-header.h"
#include "gis-webkit.h"
enum {
PROP_0,
PROP_TITLE,
PROP_SUBTITLE,
PROP_ICON_NAME,
PROP_PAINTABLE,
PROP_SHOW_ICON,
PROP_LAST,
};
static GParamSpec *obj_props[PROP_LAST];
struct _GisPageHeader
{
GtkBox parent;
GtkWidget *box;
GtkWidget *icon;
GtkWidget *subtitle;
GtkWidget *title;
};
G_DEFINE_TYPE (GisPageHeader, gis_page_header, GTK_TYPE_BOX)
static gboolean
is_valid_string (const gchar *s)
{
return s != NULL && g_strcmp0 (s, "") != 0;
}
static void
update_box_visibility (GisPageHeader *header)
{
gtk_widget_set_visible (header->box, gtk_widget_get_visible (header->subtitle) ||
gtk_widget_get_visible (header->title));
}
static void
gis_page_header_init (GisPageHeader *header)
{
gtk_widget_init_template (GTK_WIDGET (header));
g_signal_connect_swapped (header->subtitle, "notify::visible",
G_CALLBACK(update_box_visibility), header);
g_signal_connect_swapped (header->title, "notify::visible",
G_CALLBACK(update_box_visibility), header);
}
static void
gis_page_header_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GisPageHeader *header = GIS_PAGE_HEADER (object);
switch (prop_id)
{
case PROP_TITLE:
g_value_set_string (value, gtk_label_get_label (GTK_LABEL (header->title)));
break;
case PROP_SUBTITLE:
g_value_set_string (value, gtk_label_get_label (GTK_LABEL (header->subtitle)));
break;
case PROP_ICON_NAME:
g_object_get_property (G_OBJECT (header->icon), "icon-name", value);
break;
case PROP_PAINTABLE:
g_object_get_property (G_OBJECT (header->icon), "paintable", value);
break;
case PROP_SHOW_ICON:
g_value_set_boolean (value, gtk_widget_get_visible (header->icon));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gis_page_header_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GisPageHeader *header = GIS_PAGE_HEADER (object);
switch (prop_id)
{
case PROP_TITLE:
gtk_label_set_label (GTK_LABEL (header->title), g_value_get_string (value));
gtk_widget_set_visible (header->title, is_valid_string (g_value_get_string (value)));
break;
case PROP_SUBTITLE:
gtk_label_set_markup (GTK_LABEL (header->subtitle), g_value_get_string (value));
gtk_widget_set_visible (header->subtitle, is_valid_string (g_value_get_string (value)));
break;
case PROP_ICON_NAME:
g_object_set_property (G_OBJECT (header->icon), "icon-name", value);
break;
case PROP_PAINTABLE:
g_object_set_property (G_OBJECT (header->icon), "paintable", value);
break;
case PROP_SHOW_ICON:
gtk_widget_set_visible (header->icon, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gis_page_header_class_init (GisPageHeaderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-page-header.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, box);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, icon);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, subtitle);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisPageHeader, title);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), gis_activate_link);
gobject_class->get_property = gis_page_header_get_property;
gobject_class->set_property = gis_page_header_set_property;
obj_props[PROP_TITLE] =
g_param_spec_string ("title",
"", "",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
obj_props[PROP_SUBTITLE] =
g_param_spec_string ("subtitle",
"", "",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
obj_props[PROP_ICON_NAME] =
g_param_spec_string ("icon-name",
"", "",
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
obj_props[PROP_PAINTABLE] =
g_param_spec_object ("paintable",
"", "",
GDK_TYPE_PAINTABLE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
obj_props[PROP_SHOW_ICON] =
g_param_spec_boolean ("show-icon",
"", "",
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}

View file

@ -0,0 +1,36 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* -*- encoding: utf8 -*- */
/*
* Copyright (C) 2019 Purism SPC
*
* 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 2 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/>.
*
* Written by:
* Adrien Plazas <kekun.plazas@laposte.net>
*/
#ifndef __GIS_PAGE_HEADER_H__
#define __GIS_PAGE_HEADER_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GIS_TYPE_PAGE_HEADER (gis_page_header_get_type ())
G_DECLARE_FINAL_TYPE (GisPageHeader, gis_page_header, GIS, PAGE_HEADER, GtkBox)
G_END_DECLS
#endif /* __GIS_PAGE_HEADER_H__ */

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk30">
<template class="GisPageHeader" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkImage" id="icon">
<property name="pixel_size">96</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="box">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title">
<property name="justify">center</property>
<property name="max_width_chars">65</property>
<property name="wrap">True</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="subtitle">
<property name="justify">center</property>
<property name="max_width_chars">65</property>
<property name="wrap">True</property>
<signal name="activate-link" handler="gis_activate_link" object="GisPageHeader" swapped="no" />
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,422 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include <glib-object.h>
#include "gis-page.h"
struct _GisPagePrivate
{
char *title;
gboolean applying;
GCancellable *apply_cancel;
GisPageApplyCallback apply_cb;
gpointer apply_data;
guint complete : 1;
guint skippable : 1;
guint needs_accept : 1;
guint has_forward : 1;
guint padding : 5;
};
typedef struct _GisPagePrivate GisPagePrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GisPage, gis_page, ADW_TYPE_BIN);
enum
{
PROP_0,
PROP_DRIVER,
PROP_TITLE,
PROP_COMPLETE,
PROP_SKIPPABLE,
PROP_NEEDS_ACCEPT,
PROP_APPLYING,
PROP_SMALL_SCREEN,
PROP_HAS_FORWARD,
PROP_LAST,
};
static GParamSpec *obj_props[PROP_LAST];
static void
gis_page_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GisPage *page = GIS_PAGE (object);
GisPagePrivate *priv = gis_page_get_instance_private (page);
switch (prop_id)
{
case PROP_DRIVER:
g_value_set_object (value, page->driver);
break;
case PROP_TITLE:
g_value_set_string (value, priv->title);
break;
case PROP_COMPLETE:
g_value_set_boolean (value, priv->complete);
break;
case PROP_SKIPPABLE:
g_value_set_boolean (value, priv->skippable);
break;
case PROP_NEEDS_ACCEPT:
g_value_set_boolean (value, priv->needs_accept);
break;
case PROP_HAS_FORWARD:
g_value_set_boolean (value, priv->has_forward);
break;
case PROP_APPLYING:
g_value_set_boolean (value, gis_page_get_applying (page));
break;
case PROP_SMALL_SCREEN:
if (page->driver)
g_object_get_property (G_OBJECT (page->driver), "small-screen", value);
else
g_value_set_boolean (value, FALSE);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
small_screen_changed (GisPage *page)
{
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SMALL_SCREEN]);
}
static void
gis_page_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GisPage *page = GIS_PAGE (object);
GisPagePrivate *priv = gis_page_get_instance_private (page);
switch (prop_id)
{
case PROP_DRIVER:
page->driver = g_value_dup_object (value);
g_signal_connect_swapped (page->driver, "notify::small-screen",
G_CALLBACK (small_screen_changed), page);
small_screen_changed (page);
break;
case PROP_TITLE:
gis_page_set_title (page, (char *) g_value_get_string (value));
break;
case PROP_SKIPPABLE:
priv->skippable = g_value_get_boolean (value);
break;
case PROP_NEEDS_ACCEPT:
priv->needs_accept = g_value_get_boolean (value);
break;
case PROP_HAS_FORWARD:
priv->has_forward = g_value_get_boolean (value);
break;
case PROP_COMPLETE:
priv->complete = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gis_page_finalize (GObject *object)
{
GisPage *page = GIS_PAGE (object);
GisPagePrivate *priv = gis_page_get_instance_private (page);
g_free (priv->title);
g_assert (!priv->applying);
g_assert (priv->apply_cb == NULL);
g_assert (priv->apply_cancel == NULL);
G_OBJECT_CLASS (gis_page_parent_class)->finalize (object);
}
static void
gis_page_dispose (GObject *object)
{
GisPage *page = GIS_PAGE (object);
GisPagePrivate *priv = gis_page_get_instance_private (page);
if (priv->apply_cancel)
g_cancellable_cancel (priv->apply_cancel);
if (page->driver)
g_signal_handlers_disconnect_by_func (page->driver, small_screen_changed, page);
g_clear_object (&page->driver);
G_OBJECT_CLASS (gis_page_parent_class)->dispose (object);
}
static void
gis_page_constructed (GObject *object)
{
GisPage *page = GIS_PAGE (object);
gis_page_locale_changed (page);
G_OBJECT_CLASS (gis_page_parent_class)->constructed (object);
}
static gboolean
gis_page_real_apply (GisPage *page,
GCancellable *cancellable)
{
return FALSE;
}
static void
gis_page_class_init (GisPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gis_page_constructed;
object_class->dispose = gis_page_dispose;
object_class->finalize = gis_page_finalize;
object_class->get_property = gis_page_get_property;
object_class->set_property = gis_page_set_property;
klass->apply = gis_page_real_apply;
obj_props[PROP_DRIVER] =
g_param_spec_object ("driver", "", "", GIS_TYPE_DRIVER,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
obj_props[PROP_TITLE] =
g_param_spec_string ("title", "", "", "",
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
obj_props[PROP_COMPLETE] =
g_param_spec_boolean ("complete", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
obj_props[PROP_SKIPPABLE] =
g_param_spec_boolean ("skippable", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
obj_props[PROP_NEEDS_ACCEPT] =
g_param_spec_boolean ("needs-accept", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
obj_props[PROP_HAS_FORWARD] =
g_param_spec_boolean ("has-forward", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
obj_props[PROP_APPLYING] =
g_param_spec_boolean ("applying", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
obj_props[PROP_SMALL_SCREEN] =
g_param_spec_boolean ("small-screen", "", "", FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
}
static void
gis_page_init (GisPage *page)
{
}
char *
gis_page_get_title (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
if (priv->title != NULL)
return priv->title;
else
return "";
}
void
gis_page_set_title (GisPage *page, char *title)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
g_clear_pointer (&priv->title, g_free);
priv->title = g_strdup (title);
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_TITLE]);
}
gboolean
gis_page_get_complete (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
return priv->complete;
}
void
gis_page_set_complete (GisPage *page, gboolean complete)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
priv->complete = complete;
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_COMPLETE]);
}
gboolean
gis_page_get_skippable (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
return priv->skippable;
}
void
gis_page_set_skippable (GisPage *page, gboolean skippable)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
priv->skippable = skippable;
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SKIPPABLE]);
}
gboolean
gis_page_get_needs_accept (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
return priv->needs_accept;
}
void
gis_page_set_needs_accept (GisPage *page, gboolean needs_accept)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
priv->needs_accept = needs_accept;
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_NEEDS_ACCEPT]);
}
gboolean
gis_page_get_has_forward (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
return priv->has_forward;
}
void
gis_page_set_has_forward (GisPage *page, gboolean has_forward)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
if (priv->has_forward != has_forward)
{
priv->has_forward = has_forward;
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_HAS_FORWARD]);
}
}
void
gis_page_locale_changed (GisPage *page)
{
if (GIS_PAGE_GET_CLASS (page)->locale_changed)
return GIS_PAGE_GET_CLASS (page)->locale_changed (page);
}
void
gis_page_apply_begin (GisPage *page,
GisPageApplyCallback callback,
gpointer user_data)
{
GisPageClass *klass;
GisPagePrivate *priv = gis_page_get_instance_private (page);
g_return_if_fail (GIS_IS_PAGE (page));
g_return_if_fail (priv->applying == FALSE);
klass = GIS_PAGE_GET_CLASS (page);
priv->apply_cb = callback;
priv->apply_data = user_data;
priv->apply_cancel = g_cancellable_new ();
priv->applying = TRUE;
if (!klass->apply (page, priv->apply_cancel))
{
/* Shortcut case where we don't want apply, to avoid flicker */
gis_page_apply_complete (page, TRUE);
}
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APPLYING]);
}
void
gis_page_apply_complete (GisPage *page,
gboolean valid)
{
GisPageApplyCallback callback;
gpointer user_data;
GisPagePrivate *priv = gis_page_get_instance_private (page);
g_return_if_fail (GIS_IS_PAGE (page));
g_return_if_fail (priv->applying == TRUE);
callback = priv->apply_cb;
priv->apply_cb = NULL;
user_data = priv->apply_data;
priv->apply_data = NULL;
g_clear_object (&priv->apply_cancel);
priv->applying = FALSE;
g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APPLYING]);
if (callback)
(callback) (page, valid, user_data);
}
gboolean
gis_page_get_applying (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
return priv->applying;
}
void
gis_page_apply_cancel (GisPage *page)
{
GisPagePrivate *priv = gis_page_get_instance_private (page);
g_cancellable_cancel (priv->apply_cancel);
}
gboolean
gis_page_save_data (GisPage *page,
GError **error)
{
if (GIS_PAGE_GET_CLASS (page)->save_data == NULL)
{
/* Not implemented, which presumably means the page has nothing to save. */
return TRUE;
}
return GIS_PAGE_GET_CLASS (page)->save_data (page, error);
}
void
gis_page_shown (GisPage *page)
{
if (GIS_PAGE_GET_CLASS (page)->shown)
GIS_PAGE_GET_CLASS (page)->shown (page);
}
void
gis_page_skip (GisPage *page)
{
if (GIS_PAGE_GET_CLASS (page)->skip)
GIS_PAGE_GET_CLASS (page)->skip (page);
}

View file

@ -0,0 +1,92 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GIS_PAGE_H__
#define __GIS_PAGE_H__
#include "gnome-initial-setup.h"
#include <adwaita.h>
G_BEGIN_DECLS
#define GIS_TYPE_PAGE (gis_page_get_type ())
#define GIS_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_PAGE, GisPage))
#define GIS_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_PAGE, GisPageClass))
#define GIS_IS_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_PAGE))
#define GIS_IS_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_PAGE))
#define GIS_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_PAGE, GisPageClass))
typedef struct _GisPage GisPage;
typedef struct _GisPageClass GisPageClass;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GisPage, g_object_unref)
typedef void (* GisPageApplyCallback) (GisPage *page,
gboolean valid,
gpointer user_data);
struct _GisPage
{
AdwBin parent;
GisDriver *driver;
};
struct _GisPageClass
{
AdwBinClass parent_class;
char *page_id;
void (*locale_changed) (GisPage *page);
gboolean (*apply) (GisPage *page,
GCancellable *cancellable);
gboolean (*save_data) (GisPage *page,
GError **error);
void (*shown) (GisPage *page);
void (*skip) (GisPage *page);
};
GType gis_page_get_type (void);
char * gis_page_get_title (GisPage *page);
void gis_page_set_title (GisPage *page, char *title);
gboolean gis_page_get_complete (GisPage *page);
void gis_page_set_complete (GisPage *page, gboolean complete);
gboolean gis_page_get_skippable (GisPage *page);
void gis_page_set_skippable (GisPage *page, gboolean skippable);
gboolean gis_page_get_needs_accept (GisPage *page);
void gis_page_set_needs_accept (GisPage *page, gboolean needs_accept);
gboolean gis_page_get_has_forward (GisPage *page);
void gis_page_set_has_forward (GisPage *page, gboolean has_forward);
void gis_page_locale_changed (GisPage *page);
void gis_page_apply_begin (GisPage *page, GisPageApplyCallback callback, gpointer user_data);
void gis_page_apply_cancel (GisPage *page);
void gis_page_apply_complete (GisPage *page, gboolean valid);
gboolean gis_page_get_applying (GisPage *page);
gboolean gis_page_save_data (GisPage *page,
GError **error);
void gis_page_shown (GisPage *page);
void gis_page_skip (GisPage *page);
G_END_DECLS
#endif /* __GIS_PAGE_H__ */

View file

@ -0,0 +1,59 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015-2016 Red Hat
* Copyright (C) 2015-2017 Endless OS Foundation LLC
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by:
* Dan Nicholson <dbn@endlessos.org>
* Will Thompson <wjt@endlessos.org>
*/
#include "gis-pkexec.h"
gboolean
gis_pkexec (const gchar *command,
const gchar *arg1,
const gchar *user,
GError **error)
{
g_autoptr(GSubprocessLauncher) launcher = NULL;
g_autoptr(GSubprocess) process = NULL;
const gchar * const root_argv[] = { "pkexec", command, arg1, NULL };
const gchar * const user_argv[] = { "pkexec", "--user", user, command, arg1, NULL };
const gchar * const *argv = user == NULL ? root_argv : user_argv;
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
/* pkexec won't let us run the program if $SHELL isn't in /etc/shells,
* so remove it from the environment.
*/
g_subprocess_launcher_unsetenv (launcher, "SHELL");
process = g_subprocess_launcher_spawnv (launcher, argv, error);
if (!process) {
g_prefix_error (error, "Failed to create %s process: ", command);
return FALSE;
}
if (!g_subprocess_wait_check (process, NULL, error)) {
g_prefix_error (error, "%s failed: ", command);
return FALSE;
}
return TRUE;
}

View file

@ -0,0 +1,41 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015-2016 Red Hat
* Copyright (C) 2015-2017 Endless OS Foundation LLC
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by:
* Dan Nicholson <dbn@endlessos.org>
* Will Thompson <wjt@endlessos.org>
*/
#ifndef __GIS_PKEXEC_H__
#define __GIS_PKEXEC_H__
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
gboolean
gis_pkexec (const gchar *command,
const gchar *arg1,
const gchar *user,
GError **error);
G_END_DECLS
#endif /* __GIS_PKEXEC_H__ */

View file

@ -0,0 +1,33 @@
/*
* Copyright 2023 Endless OS Foundation LLC
*
* 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gis-util.h"
void
gis_add_style_from_resource (const char *resource_path)
{
g_autoptr(GtkCssProvider) provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider, resource_path);
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}

View file

@ -0,0 +1,19 @@
/*
* Copyright 2023 Endless OS Foundation LLC
*
* 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 2 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/>.
*/
#pragma once
void gis_add_style_from_resource (const char *path);

View file

@ -0,0 +1,112 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015, 2024 Red Hat Inc.
*
* 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gis-webkit.h"
#include <glib/gi18n.h>
#ifdef HAVE_WEBKITGTK
#include <webkit/webkit.h>
#endif
#ifdef HAVE_WEBKITGTK
static void
notify_progress_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GtkWidget *progress_bar = user_data;
WebKitWebView *web_view = WEBKIT_WEB_VIEW (object);
gdouble progress;
progress = webkit_web_view_get_estimated_load_progress (web_view);
gtk_widget_set_visible (progress_bar, progress != 1.0);
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar), progress);
}
static void
notify_title_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GtkWindow *dialog = user_data;
WebKitWebView *web_view = WEBKIT_WEB_VIEW (object);
gtk_window_set_title (dialog, webkit_web_view_get_title (web_view));
}
gboolean
gis_activate_link (GtkLabel *label,
const gchar *uri,
GtkWidget *any_widget)
{
GtkWidget *headerbar;
GtkWidget *dialog;
GtkWidget *overlay;
GtkWidget *view;
GtkWidget *progress_bar;
headerbar = gtk_header_bar_new ();
gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (headerbar), TRUE);
dialog = g_object_new (GTK_TYPE_WINDOW,
"destroy-with-parent", TRUE,
"transient-for", gtk_widget_get_root (any_widget),
"titlebar", headerbar,
"title", "", /* use empty title until it can be filled, instead of briefly flashing the default title */
"modal", TRUE,
"default-width", 800,
"default-height", 600,
NULL);
overlay = gtk_overlay_new ();
gtk_window_set_child (GTK_WINDOW (dialog), overlay);
progress_bar = gtk_progress_bar_new ();
gtk_widget_add_css_class (progress_bar, "osd");
gtk_widget_set_halign (progress_bar, GTK_ALIGN_FILL);
gtk_widget_set_valign (progress_bar, GTK_ALIGN_START);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), progress_bar);
view = webkit_web_view_new ();
gtk_widget_set_hexpand (view, TRUE);
gtk_widget_set_vexpand (view, TRUE);
g_signal_connect_object (view, "notify::estimated-load-progress",
G_CALLBACK (notify_progress_cb), progress_bar, 0);
g_signal_connect_object (view, "notify::title",
G_CALLBACK (notify_title_cb), dialog, 0);
gtk_overlay_set_child (GTK_OVERLAY (overlay), view);
gtk_window_present (GTK_WINDOW (dialog));
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), uri);
return TRUE;
}
#else
gboolean
gis_activate_link (GtkLabel *label,
const gchar *uri,
GtkWidget *any_widget)
{
/* Fall back to default handler */
return FALSE;
}
#endif

View file

@ -0,0 +1,29 @@
/*
* Copyright 2024 Red Hat Inc.
*
* 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 2 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/>.
*/
#pragma once
#include <glib.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
gboolean gis_activate_link (GtkLabel *label,
const gchar *uri,
GtkWidget *any_widget);
G_END_DECLS

View file

@ -0,0 +1,318 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Copies settings installed from gnome-initial-setup and
* sticks them in the user's profile */
#include "config.h"
/* For futimens and nanosecond struct stat fields. */
#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200809L
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include <pwd.h>
#include <string.h>
#include <locale.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_XATTR
#include <sys/xattr.h>
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
static char *
get_gnome_initial_setup_home_dir (void)
{
struct passwd pw, *pwp;
char buf[4096];
getpwnam_r ("gnome-initial-setup", &pw, buf, sizeof (buf), &pwp);
if (pwp != NULL)
return g_strdup (pwp->pw_dir);
else
return NULL;
}
static gboolean
file_is_ours (const char *path)
{
GStatBuf buf;
if (g_stat (path, &buf) != 0) {
if (errno != ENOENT)
g_warning ("Could not get information on \"%s\": %s",
path,
g_strerror (errno));
return FALSE;
}
return buf.st_uid == geteuid ();
}
static void
copy_file_mode (GStatBuf *src_stat,
const char *dest,
int dest_fd)
{
if (fchmod (dest_fd, src_stat->st_mode) == -1) {
g_warning ("Could not set file permissions for %s: %s", dest, g_strerror (errno));
return;
}
}
/* Copy the source file modification time. Like g_file_copy, access time
* is not changed.
*/
static void
copy_file_modtime (GStatBuf *src_stat,
const char *dest,
int dest_fd)
{
struct timespec times[2];
times[0].tv_sec = 0;
times[0].tv_nsec = UTIME_OMIT;
times[1] = src_stat->st_mtim;
if (futimens (dest_fd, times) == -1) {
g_warning ("Could not set file timestamps for %s: %s", dest, g_strerror (errno));
return;
}
}
static void
copy_file_xattrs (const char *src,
int src_fd,
const char *dest,
int dest_fd)
{
#ifdef HAVE_XATTR
g_autofree char *attrs = NULL;
ssize_t attrs_size;
ssize_t buf_size;
char *key;
buf_size = 0;
while (1) {
attrs_size = flistxattr (src_fd, attrs, buf_size);
if (attrs_size <= buf_size)
break;
buf_size = attrs_size;
attrs = g_realloc (attrs, buf_size);
}
if (attrs_size == -1) {
g_warning ("Could not get extended attrs for %s: %s", src, g_strerror (errno));
return;
} else if (attrs_size == 0) {
g_debug ("%s has no extended attributes", src);
return;
}
key = attrs;
while (attrs_size > 0) {
g_autofree char *value = NULL;
ssize_t value_size;
size_t key_len;
/* Skip SELinux context so the policy default is used. */
if (g_strcmp0 (key, "security.selinux") == 0)
goto next_attr;
buf_size = 0;
while (1) {
value_size = fgetxattr (src_fd, key, value, buf_size);
if (value_size <= buf_size)
break;
buf_size = value_size;
value = g_realloc (value, buf_size);
}
if (value_size == -1) {
g_warning ("Could not get extended attr %s for %s: %s", key, src, g_strerror (errno));
goto next_attr;
}
if (fsetxattr (dest_fd, key, value, value_size, 0) == -1) {
g_warning ("Could not set extended attr %s for %s: %s", key, src, g_strerror (errno));
goto next_attr;
}
next_attr:
key_len = strlen (key);
key += key_len + 1;
attrs_size -= key_len + 1;
}
#endif /* HAVE_XATTR */
}
static void
copy_file (const char *src,
const char *dest,
GStatBuf *src_stat)
{
int src_fd = -1;
int dest_fd = -1;
mode_t dest_mode;
guint8 read_buf[8192];
const guint8 *write_buf;
ssize_t bytes_read;
ssize_t bytes_to_write;
ssize_t bytes_written;
src_fd = g_open (src, O_RDONLY | O_BINARY, 0);
if (src_fd == -1) {
g_warning ("Could not open %s: %s", src, g_strerror (errno));
goto out;
}
dest_mode = src_stat->st_mode & ~S_IFMT;
dest_fd = g_open (dest, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, dest_mode);
if (dest_fd == -1) {
g_warning ("Could not open %s: %s", dest, g_strerror (errno));
goto out;
}
while (1) {
bytes_read = read (src_fd, read_buf, sizeof (read_buf));
if (bytes_read == 0) {
break;
} else if (bytes_read == -1) {
if (errno == EINTR)
continue;
g_warning ("Unable to read %s: %s", src, g_strerror (errno));
goto out;
}
write_buf = read_buf;
bytes_to_write = bytes_read;
while (bytes_to_write > 0) {
bytes_written = write (dest_fd, write_buf, bytes_to_write);
if (bytes_written == -1) {
if (errno == EINTR)
continue;
g_warning ("Unable to write %s: %s", dest, g_strerror (errno));
goto out;
}
write_buf += bytes_written;
bytes_to_write -= bytes_written;
}
}
copy_file_mode (src_stat, dest, dest_fd);
copy_file_modtime (src_stat, dest, dest_fd);
copy_file_xattrs (src, src_fd, dest, dest_fd);
out:
if (src_fd >= 0)
g_close (src_fd, NULL);
if (dest_fd >= 0)
g_close (dest_fd, NULL);
}
static void
copy_file_from_homedir (const gchar *src_base,
const gchar *dest_base,
const gchar *path)
{
g_autofree gchar *dest = g_build_filename (dest_base, path, NULL);
g_autofree gchar *dest_parent = g_path_get_dirname (dest);
g_autofree gchar *src = g_build_filename (src_base, path, NULL);
GStatBuf src_stat;
g_debug ("Copying %s to %s", src, dest);
if (g_lstat (src, &src_stat) == -1) {
g_debug ("Cannot copy %s: %s", src, g_strerror (errno));
return;
}
/* Only regular files are supported */
if (!S_ISREG (src_stat.st_mode)) {
if (S_ISDIR (src_stat.st_mode)) {
g_warning ("Skipping directory %s", src);
return;
} else {
g_warning ("Skipping non-file %s", src);
return;
}
}
if (g_mkdir_with_parents (dest_parent, 0777) != 0) {
g_warning ("Unable to create directory %s: %s", dest_parent, g_strerror (errno));
return;
}
copy_file (src, dest, &src_stat);
}
int
main (int argc,
char **argv)
{
g_autofree char *src = NULL;
g_autofree char *dest = NULL;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) error = NULL;
GOptionEntry entries[] = {
{ "src", 0, 0, G_OPTION_ARG_FILENAME, &src,
"Source path (default: home directory)", NULL },
{ "dest", 0, 0, G_OPTION_ARG_FILENAME, &dest,
"Destination path (default: gnome-initial-setup home directory)", NULL },
{ NULL }
};
setlocale (LC_ALL, "");
context = g_option_context_new ("— GNOME initial setup copy worker");
g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error)) {
g_printerr ("Error parsing arguments: %s\n", error->message);
exit (EXIT_FAILURE);
}
if (src == NULL) {
src = get_gnome_initial_setup_home_dir ();
if (src == NULL) {
g_debug ("Could not determine gnome-initial-setup homedir");
exit (EXIT_SUCCESS);
}
}
if (dest == NULL)
dest = g_strdup (g_get_home_dir ());
if (g_access (src, F_OK) != 0) {
g_debug ("Initial setup homedir %s does not exist", src);
exit (EXIT_SUCCESS);
}
if (!file_is_ours (src)) {
g_warning ("Initial setup homedir %s is not owned by UID %u",
src,
geteuid ());
exit (EXIT_SUCCESS);
}
#define FILE(path) \
copy_file_from_homedir (src, dest, path);
FILE (".config/gnome-initial-setup-done");
FILE (".config/dconf/user");
FILE (".config/monitors.xml");
FILE (".local/share/keyrings/login.keyring");
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,383 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include "gnome-initial-setup.h"
#include <adwaita.h>
#include <pwd.h>
#include <unistd.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include "pages/welcome/gis-welcome-page.h"
#include "pages/language/gis-language-page.h"
#include "pages/keyboard/gis-keyboard-page.h"
#include "pages/network/gis-network-page.h"
#include "pages/timezone/gis-timezone-page.h"
#include "pages/privacy/gis-privacy-page.h"
#include "pages/software/gis-software-page.h"
#include "pages/account/gis-account-pages.h"
#include "pages/parental-controls/gis-parental-controls-page.h"
#include "pages/password/gis-password-page.h"
#include "pages/summary/gis-summary-page.h"
#define VENDOR_PAGES_GROUP "pages"
#define VENDOR_SKIP_KEY "skip"
#define VENDOR_NEW_USER_ONLY_KEY "new_user_only"
#define VENDOR_EXISTING_USER_ONLY_KEY "existing_user_only"
static gboolean force_existing_user_mode;
static GPtrArray *skipped_pages;
typedef GisPage *(*PreparePage) (GisDriver *driver);
typedef struct {
const gchar *page_id;
PreparePage prepare_page_func;
gboolean new_user_only;
} PageData;
#define PAGE(name, new_user_only) { #name, gis_prepare_ ## name ## _page, new_user_only }
static PageData page_table[] = {
PAGE (welcome, FALSE),
PAGE (language, FALSE),
PAGE (keyboard, FALSE),
PAGE (network, FALSE),
PAGE (privacy, FALSE),
PAGE (timezone, TRUE),
PAGE (software, TRUE),
PAGE (account, TRUE),
PAGE (password, TRUE),
#ifdef HAVE_PARENTAL_CONTROLS
PAGE (parental_controls, TRUE),
PAGE (parent_password, TRUE),
#endif
PAGE (summary, FALSE),
{ NULL },
};
#undef PAGE
static gboolean
should_skip_page (const gchar *page_id,
gchar **skip_pages)
{
guint i = 0;
/* special case welcome. We only want to show it if language
* is skipped
*/
if (strcmp (page_id, "welcome") == 0)
return !should_skip_page ("language", skip_pages);
/* check through our skip pages list for pages we don't want */
if (skip_pages) {
while (skip_pages[i]) {
if (g_strcmp0 (skip_pages[i], page_id) == 0)
return TRUE;
i++;
}
}
return FALSE;
}
static gchar **
strv_append (gchar **a,
gchar **b)
{
guint n = g_strv_length (a);
guint m = g_strv_length (b);
a = g_renew (gchar *, a, n + m + 1);
for (guint i = 0; i < m; i++)
a[n + i] = g_strdup (b[i]);
a[n + m] = NULL;
return a;
}
static gchar **
pages_to_skip_from_file (GisDriver *driver,
gboolean is_new_user)
{
GStrv skip_pages = NULL;
GStrv additional_skip_pages = NULL;
/* This code will read the keyfile containing vendor customization options and
* look for options under the "pages" group, and supports the following keys:
* - skip (optional): list of pages to be skipped always
* - new_user_only (optional): list of pages to be skipped in existing user mode
* - existing_user_only (optional): list of pages to be skipped in new user mode
*
* This is how this file might look on a vendor image:
*
* [pages]
* skip=timezone
* existing_user_only=language;keyboard
*/
skip_pages = gis_driver_conf_get_string_list (driver, VENDOR_PAGES_GROUP,
VENDOR_SKIP_KEY, NULL);
additional_skip_pages =
gis_driver_conf_get_string_list (driver, VENDOR_PAGES_GROUP,
is_new_user ? VENDOR_EXISTING_USER_ONLY_KEY : VENDOR_NEW_USER_ONLY_KEY,
NULL);
if (!skip_pages && additional_skip_pages) {
skip_pages = additional_skip_pages;
} else if (skip_pages && additional_skip_pages) {
skip_pages = strv_append (skip_pages, additional_skip_pages);
g_strfreev (additional_skip_pages);
}
return skip_pages;
}
static void
destroy_pages_after (GisAssistant *assistant,
GisPage *page)
{
GList *pages, *l, *next;
pages = gis_assistant_get_all_pages (assistant);
for (l = pages; l != NULL; l = l->next)
if (l->data == page)
break;
l = l->next;
for (; l != NULL; l = next) {
next = l->next;
gis_assistant_remove_page (assistant, l->data);
}
}
static void
destroy_page (gpointer data)
{
GtkWidget *assistant;
GisPage *page;
page = data;
assistant = gtk_widget_get_ancestor (GTK_WIDGET (page), GIS_TYPE_ASSISTANT);
if (assistant)
gis_assistant_remove_page (GIS_ASSISTANT (assistant), page);
}
static void
rebuild_pages_cb (GisDriver *driver)
{
PageData *page_data;
GisPage *page;
GisAssistant *assistant;
GisPage *current_page;
gchar **skip_pages;
gboolean is_new_user, skipped;
assistant = gis_driver_get_assistant (driver);
current_page = gis_assistant_get_current_page (assistant);
page_data = page_table;
g_ptr_array_free (skipped_pages, TRUE);
skipped_pages = g_ptr_array_new_with_free_func (destroy_page);
if (current_page != NULL) {
destroy_pages_after (assistant, current_page);
for (page_data = page_table; page_data->page_id != NULL; ++page_data)
if (g_str_equal (page_data->page_id, GIS_PAGE_GET_CLASS (current_page)->page_id))
break;
++page_data;
}
is_new_user = (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER);
skip_pages = pages_to_skip_from_file (driver, is_new_user);
for (; page_data->page_id != NULL; ++page_data) {
skipped = FALSE;
if ((page_data->new_user_only && !is_new_user) ||
(should_skip_page (page_data->page_id, skip_pages)))
skipped = TRUE;
page = page_data->prepare_page_func (driver);
if (!page)
continue;
if (skipped) {
gis_page_skip (page);
g_ptr_array_add (skipped_pages, page);
} else {
gis_driver_add_page (driver, page);
}
}
g_strfreev (skip_pages);
}
static GisDriverMode
get_mode (void)
{
if (force_existing_user_mode)
return GIS_DRIVER_MODE_EXISTING_USER;
else
return GIS_DRIVER_MODE_NEW_USER;
}
static gboolean
initial_setup_disabled_by_anaconda (void)
{
const gchar *file_name = SYSCONFDIR "/sysconfig/anaconda";
g_autoptr(GError) error = NULL;
g_autoptr(GKeyFile) key_file = g_key_file_new ();
if (!g_key_file_load_from_file (key_file, file_name, G_KEY_FILE_NONE, &error)) {
if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) &&
!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
g_warning ("Could not read %s: %s", file_name, error->message);
}
return FALSE;
}
return g_key_file_get_boolean (key_file, "General", "post_install_tools_disabled", NULL);
}
int
main (int argc, char *argv[])
{
GisDriver *driver;
int status;
GOptionContext *context;
GisDriverMode mode;
GOptionEntry entries[] = {
{ "existing-user", 0, 0, G_OPTION_ARG_NONE, &force_existing_user_mode,
_("Force existing user mode"), NULL },
{ NULL }
};
g_unsetenv ("GIO_USE_VFS");
/* By default, libadwaita reads settings from the Settings portal, which causes
* the portal to be started, which causes gnome-keyring to be started. This
* interferes with our attempt below to manually start gnome-keyring and set
* the login keyring password to a well-known value, which we overwrite with
* the user's password once they choose one.
*/
g_setenv ("ADW_DISABLE_PORTAL", "1", TRUE);
context = g_option_context_new (_("— GNOME initial setup"));
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_parse (context, &argc, &argv, NULL);
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
g_message ("Starting gnome-initial-setup");
if (gis_get_mock_mode ())
g_message ("Mock mode: changes will not be saved to disk");
else
g_message ("Production mode: changes will be saved to disk");
skipped_pages = g_ptr_array_new_with_free_func (destroy_page);
mode = get_mode ();
/* When we are running as the gnome-initial-setup user we
* dont have a normal user session and need to initialize
* the keyring manually so that we can pass the credentials
* along to the new user in the handoff.
*/
if (mode == GIS_DRIVER_MODE_NEW_USER && !gis_get_mock_mode ())
gis_ensure_login_keyring ();
driver = gis_driver_new (mode);
/* On first login, GNOME Shell offers to run a tour. If we also run Initial
* Setup, the two immovable, centred windows will sit atop one another.
* Until we have the ability to run Initial Setup in the "kiosk" mode, like
* it does in new-user mode, disable Initial Setup for existing users.
*
* https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/120#note_1019004
* https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/12
*/
if (mode == GIS_DRIVER_MODE_EXISTING_USER) {
g_message ("Skipping gnome-initial-setup for existing user");
gis_ensure_stamp_files (driver);
exit (EXIT_SUCCESS);
}
/* We only do this in existing-user mode, because if gdm launches us
* in new-user mode and we just exit, gdm's special g-i-s session
* never terminates. */
if (initial_setup_disabled_by_anaconda () &&
mode == GIS_DRIVER_MODE_EXISTING_USER) {
gis_ensure_stamp_files (driver);
exit (EXIT_SUCCESS);
}
g_signal_connect (driver, "rebuild-pages", G_CALLBACK (rebuild_pages_cb), NULL);
status = g_application_run (G_APPLICATION (driver), argc, argv);
g_ptr_array_free (skipped_pages, TRUE);
g_object_unref (driver);
g_option_context_free (context);
return status;
}
void
gis_ensure_stamp_files (GisDriver *driver)
{
g_autofree gchar *done_file = NULL;
g_autoptr(GError) error = NULL;
done_file = g_build_filename (g_get_user_config_dir (), "gnome-initial-setup-done", NULL);
if (!g_file_set_contents (done_file, "yes", -1, &error)) {
g_warning ("Unable to create %s: %s", done_file, error->message);
g_clear_error (&error);
}
}
/**
* gis_get_mock_mode:
*
* Gets whether gnome-initial-setup has been built for development, and hence
* shouldnt permanently change any system configuration.
*
* By default, mock mode is enabled when running in a build environment. This
* heuristic may be changed in future.
*
* Returns: %TRUE if in mock mode, %FALSE otherwise
*/
gboolean
gis_get_mock_mode (void)
{
return (g_getenv ("UNDER_JHBUILD") != NULL);
}

View file

@ -0,0 +1,44 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GNOME_INITIAL_SETUP_H__
#define __GNOME_INITIAL_SETUP_H__
#include <gtk/gtk.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
typedef struct _GisDriver GisDriver;
typedef struct _GisAssistant GisAssistant;
typedef struct _GisPage GisPage;
#include "gis-driver.h"
#include "gis-assistant.h"
#include "gis-page.h"
#include "gis-pkexec.h"
#include "gis-keyring.h"
#include "gis-util.h"
void gis_ensure_stamp_files (GisDriver *driver);
gboolean gis_get_mock_mode (void);
#endif /* __GNOME_INITIAL_SETUP_H__ */

View file

@ -0,0 +1,115 @@
sources = []
resources = gnome.compile_resources(
'gis-assistant-resources',
files('gis-assistant.gresource.xml'),
c_name: 'gis_assistant'
)
sources += [
resources,
'cc-common-language.c',
'gnome-initial-setup.c',
'gis-assistant.c',
'gis-page.c',
'gis-page-header.c',
'gis-pkexec.c',
'gis-driver.c',
'gis-keyring.c',
'gis-util.c',
'gis-webkit.c',
'gnome-initial-setup.h',
'gis-assistant.h',
'gis-page.h',
'gis-page-header.h',
'gis-pkexec.h',
'gis-driver.h',
'gis-keyring.h'
]
glib_dep = dependency ('glib-2.0', version: '>= 2.63.1')
gio_dep = dependency ('gio-unix-2.0', version: '>= 2.53.0')
geocode_glib_2_dep = dependency('geocode-glib-2.0')
gweather_dep = dependency('gweather4')
subdir('pages')
dependencies = [
dependency ('libnm', version: '>= 1.2'),
dependency ('libnma-gtk4', version: '>= 1.0'),
dependency ('polkit-gobject-1', version: '>= 0.103'),
dependency ('accountsservice'),
geocode_glib_2_dep,
dependency ('gnome-desktop-4'),
dependency ('gsettings-desktop-schemas', version: '>= 3.37.1'),
dependency ('fontconfig'),
dependency ('gtk4', version: '>= 4.17', fallback: ['gtk4', 'libgtk_dep']),
glib_dep,
gio_dep,
dependency ('gdm', version: '>= 3.8.3'),
gweather_dep,
dependency ('libgeoclue-2.0', version: '>= 2.6.0'),
cc.find_library('m', required: false),
dependency ('pango', version: '>= 1.32.5'),
dependency ('json-glib-1.0'),
dependency ('krb5'),
dependency ('libsecret-1', version: '>= 0.18.8'),
dependency ('pwquality'),
ibus_dep,
libmalcontent_dep,
libmalcontent_ui_dep,
libadwaita_dep,
webkitgtk_dep
]
executable(
'gnome-initial-setup',
sources,
include_directories: config_h_dir,
dependencies: dependencies,
install: true,
install_dir: get_option('libexecdir')
)
copy_worker_dependencies = [
glib_dep,
]
copy_worker = executable(
'gnome-initial-setup-copy-worker',
['gnome-initial-setup-copy-worker.c'],
include_directories: config_h_dir,
dependencies: copy_worker_dependencies,
install: true,
install_dir: get_option('libexecdir')
)
test_env = [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
]
test_copy_worker_dependencies = [
glib_dep,
gio_dep,
]
test_copy_worker_env = test_env + [
'COPY_WORKER_PATH=@0@'.format(copy_worker.full_path()),
]
test_copy_worker = executable(
'test-copy-worker',
['test-copy-worker.c'],
include_directories: config_h_dir,
dependencies: test_copy_worker_dependencies,
)
test(
'test-copy-worker',
test_copy_worker,
env: test_copy_worker_env,
protocol: 'tap',
)

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/initial-setup">
<file preprocess="xml-stripblanks" alias="gis-account-avatar-chooser.ui">gis-account-avatar-chooser.ui</file>
<file preprocess="xml-stripblanks" alias="gis-account-page.ui">gis-account-page.ui</file>
<file preprocess="xml-stripblanks" alias="gis-account-page-local.ui">gis-account-page-local.ui</file>
<file preprocess="xml-stripblanks" alias="gis-account-page-enterprise.ui">gis-account-page-enterprise.ui</file>
<file alias="gis-account-page.css">gis-account-page.css</file>
</gresource>
</gresources>

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<interface>
<template class="UmPhotoDialog" parent="GtkPopover">
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkFlowBox" id="flowbox">
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="selection-mode">none</property>
<property name="max-children-per-line">5</property>
<property name="homogeneous">True</property>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,894 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include "gis-account-page-enterprise.h"
#include "gnome-initial-setup.h"
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <act/act-user-manager.h>
#include "um-realm-manager.h"
#include "um-utils.h"
#include "gis-page-header.h"
static void join_show_prompt (GisAccountPageEnterprise *page,
GError *error);
static void on_join_login (GObject *source,
GAsyncResult *result,
gpointer user_data);
static void on_realm_joined (GObject *source,
GAsyncResult *result,
gpointer user_data);
struct _GisAccountPageEnterprise
{
AdwBin parent;
GtkWidget *header;
GtkWidget *login;
GtkWidget *password;
GtkWidget *domain;
GtkWidget *domain_entry;
GtkTreeModel *realms_model;
GtkWidget *join_dialog;
GtkWidget *join_name;
GtkWidget *join_password;
GtkWidget *join_domain;
GtkWidget *join_computer;
ActUserManager *act_client;
ActUser *act_user;
guint realmd_watch;
UmRealmManager *realm_manager;
gboolean domain_chosen;
/* Valid during apply */
UmRealmObject *realm;
GCancellable *cancellable;
gboolean join_prompted;
GisPageApplyCallback apply_complete_callback;
gpointer apply_complete_data;
};
G_DEFINE_TYPE (GisAccountPageEnterprise, gis_account_page_enterprise, ADW_TYPE_BIN);
enum {
VALIDATION_CHANGED,
USER_CACHED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
clear_password_validation_error (GtkWidget *entry)
{
gtk_widget_remove_css_class (entry, "error");
gtk_widget_set_tooltip_text (entry, NULL);
}
static void
set_password_validation_error (GtkWidget *entry,
const gchar *text)
{
gtk_widget_add_css_class (entry, "error");
gtk_widget_set_tooltip_text (entry, text);
}
static void
validation_changed (GisAccountPageEnterprise *page)
{
g_signal_emit (page, signals[VALIDATION_CHANGED], 0);
}
static void
apply_complete (GisAccountPageEnterprise *page,
gboolean valid)
{
page->apply_complete_callback (NULL, valid, page->apply_complete_data);
}
static void
show_error_dialog (GisAccountPageEnterprise *page,
const gchar *message,
GError *error)
{
GtkWidget *dialog;
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (page))),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s", message);
if (error != NULL) {
g_dbus_error_strip_remote_error (error);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
"%s", error->message);
}
g_signal_connect (dialog, "response",
G_CALLBACK (gtk_window_destroy),
NULL);
gtk_window_present (GTK_WINDOW (dialog));
}
gboolean
gis_account_page_enterprise_validate (GisAccountPageEnterprise *page)
{
const gchar *name;
gboolean valid_name;
gboolean valid_domain;
GtkTreeIter iter;
name = gtk_editable_get_text (GTK_EDITABLE (page->login));
valid_name = is_valid_name (name);
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (page->domain), &iter)) {
gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (page->domain)),
&iter, 0, &name, -1);
} else {
name = gtk_editable_get_text (GTK_EDITABLE (page->domain_entry));
}
valid_domain = is_valid_name (name);
return valid_name && valid_domain;
}
static void
on_cache_user (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
page->act_user = act_user_manager_cache_user_finish (ACT_USER_MANAGER (source),
result,
&error);
if (error != NULL) {
show_error_dialog (page, _("Failed to cache account"), error);
g_message ("Couldn't cache account: %s", error->message);
g_error_free (error);
apply_complete (page, FALSE);
return;
}
act_user_set_account_type (page->act_user,
ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR);
g_signal_emit (page,
signals[USER_CACHED],
0,
page->act_user,
gtk_editable_get_text (GTK_EDITABLE (page->password)));
apply_complete (page, TRUE);
}
static void
on_permit_user_login (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
UmRealmCommon *common;
GError *error = NULL;
gchar *login;
common = UM_REALM_COMMON (source);
um_realm_common_call_change_login_policy_finish (common, result, &error);
if (error == NULL) {
/*
* Now tell the account service about this user. The account service
* should also lookup information about this via the realm and make
* sure all that is functional.
*/
login = um_realm_calculate_login (common, gtk_editable_get_text (GTK_EDITABLE (page->login)));
g_return_if_fail (login != NULL);
g_debug ("Caching remote user: %s", login);
act_user_manager_cache_user_async (page->act_client,
login,
page->cancellable,
on_cache_user,
page);
g_free (login);
} else {
show_error_dialog (page, _("Failed to register account"), error);
g_message ("Couldn't permit logins on account: %s", error->message);
g_error_free (error);
apply_complete (page, FALSE);
}
}
static void
enterprise_permit_user_login (GisAccountPageEnterprise *page, UmRealmObject *realm)
{
UmRealmCommon *common;
gchar *login;
const gchar *add[2];
const gchar *remove[1];
GVariant *options;
common = um_realm_object_get_common (realm);
login = um_realm_calculate_login (common, gtk_editable_get_text (GTK_EDITABLE (page->login)));
g_return_if_fail (login != NULL);
add[0] = login;
add[1] = NULL;
remove[0] = NULL;
g_debug ("Permitting login for: %s", login);
options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
um_realm_common_call_change_login_policy (common, "",
add, remove, options,
page->cancellable,
on_permit_user_login,
page);
g_object_unref (common);
g_free (login);
}
static void
on_set_static_hostname (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
GVariant *retval;
retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
if (error != NULL) {
join_show_prompt (page, error);
g_error_free (error);
return;
}
g_variant_unref (retval);
/* Prompted for some admin credentials, try to use them to log in */
um_realm_login (page->realm,
gtk_editable_get_text (GTK_EDITABLE (page->join_name)),
gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
page->cancellable, on_join_login, page);
}
static void
on_join_response (GtkDialog *dialog,
gint response,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GDBusConnection *connection;
GError *error = NULL;
gchar hostname[128];
const gchar *name;
gtk_widget_set_visible (GTK_WIDGET (dialog), FALSE);
if (response != GTK_RESPONSE_OK) {
apply_complete (page, FALSE);
return;
}
name = gtk_editable_get_text (GTK_EDITABLE (page->join_computer));
if (gethostname (hostname, sizeof (hostname)) == 0 &&
!g_str_equal (name, hostname)) {
g_debug ("Setting StaticHostname to '%s'", name);
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, page->cancellable, &error);
if (error != NULL) {
apply_complete (page, FALSE);
g_warning ("Could not get DBus connection: %s", error->message);
g_error_free (error);
return;
}
g_dbus_connection_call (connection, "org.freedesktop.hostname1",
"/org/freedesktop/hostname1", "org.freedesktop.hostname1",
"SetStaticHostname",
g_variant_new ("(sb)", name, TRUE),
G_VARIANT_TYPE ("()"),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT, NULL, on_set_static_hostname, page);
} else {
name = gtk_editable_get_text (GTK_EDITABLE (page->join_name));
g_debug ("Logging in as admin user: %s", name);
/* Prompted for some admin credentials, try to use them to log in */
um_realm_login (page->realm, name,
gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
NULL, on_join_login, page);
}
}
static void
join_show_prompt (GisAccountPageEnterprise *page,
GError *error)
{
UmRealmKerberosMembership *membership;
UmRealmKerberos *kerberos;
gchar hostname[128];
const gchar *name;
gtk_editable_set_text (GTK_EDITABLE (page->join_password), "");
gtk_widget_grab_focus (GTK_WIDGET (page->join_password));
kerberos = um_realm_object_get_kerberos (page->realm);
membership = um_realm_object_get_kerberos_membership (page->realm);
gtk_label_set_text (GTK_LABEL (page->join_domain),
um_realm_kerberos_get_domain_name (kerberos));
if (gethostname (hostname, sizeof (hostname)) == 0)
gtk_editable_set_text (GTK_EDITABLE (page->join_computer), hostname);
clear_entry_validation_error (GTK_ENTRY (page->join_name));
clear_password_validation_error (page->join_password);
if (!page->join_prompted) {
name = um_realm_kerberos_membership_get_suggested_administrator (membership);
if (name && !g_str_equal (name, "")) {
g_debug ("Suggesting admin user: %s", name);
gtk_editable_set_text (GTK_EDITABLE (page->join_name), name);
} else {
gtk_widget_grab_focus (GTK_WIDGET (page->join_name));
}
} else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) {
g_debug ("Bad host name: %s", error->message);
set_entry_validation_error (GTK_ENTRY (page->join_computer), error->message);
} else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
g_debug ("Bad admin password: %s", error->message);
set_password_validation_error (page->join_password, error->message);
} else {
g_debug ("Admin login failure: %s", error->message);
g_dbus_error_strip_remote_error (error);
set_entry_validation_error (GTK_ENTRY (page->join_name), error->message);
}
g_debug ("Showing admin password dialog");
gtk_window_set_transient_for (GTK_WINDOW (page->join_dialog),
GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (page))));
gtk_window_set_modal (GTK_WINDOW (page->join_dialog), TRUE);
gtk_window_present (GTK_WINDOW (page->join_dialog));
page->join_prompted = TRUE;
g_object_unref (kerberos);
g_object_unref (membership);
/* And now we wait for on_join_response() */
}
static void
on_join_login (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
GBytes *creds;
um_realm_login_finish (page->realm, result, &creds, &error);
/* Logged in as admin successfully, use creds to join domain */
if (error == NULL) {
if (!um_realm_join_as_admin (page->realm,
gtk_editable_get_text (GTK_EDITABLE (page->join_name)),
gtk_editable_get_text (GTK_EDITABLE (page->join_password)),
creds, NULL, on_realm_joined, page)) {
show_error_dialog (page, _("No supported way to authenticate with this domain"), NULL);
g_message ("Authenticating as admin is not supported by the realm");
}
g_bytes_unref (creds);
/* Couldn't login as admin, show prompt again */
} else {
join_show_prompt (page, error);
g_message ("Couldn't log in as admin to join domain: %s", error->message);
g_error_free (error);
}
}
static void
on_realm_joined (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
um_realm_join_finish (page->realm, result, &error);
/* Yay, joined the domain, register the user locally */
if (error == NULL) {
g_debug ("Joining realm completed successfully");
enterprise_permit_user_login (page, page->realm);
/* Credential failure while joining domain, prompt for admin creds */
} else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN) ||
g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD) ||
g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME)) {
g_debug ("Joining realm failed due to credentials or host name");
join_show_prompt (page, error);
/* Other failure */
} else {
show_error_dialog (page, _("Failed to join domain"), error);
g_message ("Failed to join the domain: %s", error->message);
apply_complete (page, FALSE);
}
g_clear_error (&error);
}
static void
on_realm_login (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
GBytes *creds = NULL;
um_realm_login_finish (page->realm, result, &creds, &error);
/*
* User login is valid, but cannot authenticate right now (eg: user needs
* to change password at next login etc.)
*/
if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH)) {
g_clear_error (&error);
creds = NULL;
}
if (error == NULL) {
/* Already joined to the domain, just register this user */
if (um_realm_is_configured (page->realm)) {
g_debug ("Already joined to this realm");
enterprise_permit_user_login (page, page->realm);
/* Join the domain, try using the user's creds */
} else if (creds == NULL ||
!um_realm_join_as_user (page->realm,
gtk_editable_get_text (GTK_EDITABLE (page->login)),
gtk_editable_get_text (GTK_EDITABLE (page->password)),
creds, page->cancellable,
on_realm_joined,
page)) {
/* If we can't do user auth, try to authenticate as admin */
g_debug ("Cannot join with user credentials");
join_show_prompt (page, error);
}
g_bytes_unref (creds);
/* A problem with the user's login name or password */
} else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN)) {
g_debug ("Problem with the user's login: %s", error->message);
set_entry_validation_error (GTK_ENTRY (page->login), error->message);
gtk_widget_grab_focus (page->login);
apply_complete (page, FALSE);
} else if (g_error_matches (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD)) {
g_debug ("Problem with the user's password: %s", error->message);
set_password_validation_error (page->password, error->message);
gtk_widget_grab_focus (page->password);
apply_complete (page, FALSE);
/* Other login failure */
} else {
show_error_dialog (page, _("Failed to log into domain"), error);
g_message ("Couldn't log in as user: %s", error->message);
apply_complete (page, FALSE);
}
g_clear_error (&error);
}
static void
enterprise_check_login (GisAccountPageEnterprise *page)
{
g_assert (page->realm);
um_realm_login (page->realm,
gtk_editable_get_text (GTK_EDITABLE (page->login)),
gtk_editable_get_text (GTK_EDITABLE (page->password)),
page->cancellable,
on_realm_login,
page);
}
static void
on_realm_discover_input (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
GList *realms;
realms = um_realm_manager_discover_finish (page->realm_manager,
result, &error);
/* Found a realm, log user into domain */
if (error == NULL) {
g_assert (realms != NULL);
page->realm = g_object_ref (realms->data);
enterprise_check_login (page);
g_list_free_full (realms, g_object_unref);
} else {
/* The domain is likely invalid */
g_dbus_error_strip_remote_error (error);
g_message ("Couldn't discover domain: %s", error->message);
gtk_widget_grab_focus (page->domain_entry);
set_entry_validation_error (GTK_ENTRY (page->domain_entry), error->message);
apply_complete (page, FALSE);
g_error_free (error);
}
}
static void
enterprise_add_user (GisAccountPageEnterprise *page)
{
GtkTreeIter iter;
page->join_prompted = FALSE;
g_clear_object (&page->realm);
/* Already know about this realm, try to login as user */
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (page->domain), &iter)) {
gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (page->domain)),
&iter, 1, &page->realm, -1);
enterprise_check_login (page);
/* Something the user typed, we need to discover realm */
} else {
um_realm_manager_discover (page->realm_manager,
gtk_editable_get_text (GTK_EDITABLE (page->domain_entry)),
page->cancellable,
on_realm_discover_input,
page);
}
}
gboolean
gis_account_page_enterprise_apply (GisAccountPageEnterprise *page,
GCancellable *cancellable,
GisPageApplyCallback callback,
gpointer data)
{
GisPage *account_page = GIS_PAGE (data);
/* Parental controls are not enterprise ready. Its possible for them to have
* been enabled if the user enabled them, applied the account-local page, and
* then went back and decided to go all enterprise instead. */
gis_driver_set_parental_controls_enabled (account_page->driver, FALSE);
page->apply_complete_callback = callback;
page->apply_complete_data = data;
page->cancellable = g_object_ref (cancellable);
enterprise_add_user (page);
return TRUE;
}
static gchar *
realm_get_name (UmRealmObject *realm)
{
UmRealmCommon *common;
gchar *name;
common = um_realm_object_get_common (realm);
name = g_strdup (um_realm_common_get_name (common));
g_object_unref (common);
return name;
}
static gboolean
model_contains_realm (GtkTreeModel *model,
const gchar *realm_name)
{
gboolean contains = FALSE;
GtkTreeIter iter;
gboolean match;
gchar *name;
gboolean ret;
ret = gtk_tree_model_get_iter_first (model, &iter);
while (ret) {
gtk_tree_model_get (model, &iter, 0, &name, -1);
match = (g_strcmp0 (name, realm_name) == 0);
g_free (name);
if (match) {
g_debug ("ignoring duplicate realm: %s", realm_name);
contains = TRUE;
break;
}
ret = gtk_tree_model_iter_next (model, &iter);
}
return contains;
}
static void
enterprise_add_realm (GisAccountPageEnterprise *page,
UmRealmObject *realm)
{
GtkTreeIter iter;
gchar *name;
name = realm_get_name (realm);
/*
* Don't add a second realm if we already have one with this name.
* Sometimes realmd returns two realms for the same name, if it has
* different ways to use that realm. The first one that realmd
* returns is the one it prefers.
*/
if (!model_contains_realm (GTK_TREE_MODEL (page->realms_model), name)) {
gtk_list_store_append (GTK_LIST_STORE (page->realms_model), &iter);
gtk_list_store_set (GTK_LIST_STORE (page->realms_model), &iter,
0, name,
1, realm,
-1);
if (!page->domain_chosen && um_realm_is_configured (realm))
gtk_combo_box_set_active_iter (GTK_COMBO_BOX (page->domain), &iter);
g_debug ("added realm to drop down: %s %s", name,
g_dbus_object_get_object_path (G_DBUS_OBJECT (realm)));
}
g_free (name);
}
static void
on_manager_realm_added (UmRealmManager *manager,
UmRealmObject *realm,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
enterprise_add_realm (page, realm);
}
static void
on_realm_manager_created (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
GError *error = NULL;
GList *realms, *l;
g_clear_object (&page->realm_manager);
page->realm_manager = um_realm_manager_new_finish (result, &error);
if (error != NULL) {
g_warning ("Couldn't contact realmd service: %s", error->message);
g_error_free (error);
return;
}
/* Lookup all the realm objects */
realms = um_realm_manager_get_realms (page->realm_manager);
for (l = realms; l != NULL; l = g_list_next (l))
enterprise_add_realm (page, l->data);
g_list_free (realms);
g_signal_connect (page->realm_manager, "realm-added",
G_CALLBACK (on_manager_realm_added), page);
/* When no realms try to discover a sensible default, triggers realm-added signal */
um_realm_manager_discover (page->realm_manager, "", NULL, NULL, NULL);
gtk_widget_set_visible (GTK_WIDGET (page), TRUE);
}
static void
on_realmd_appeared (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
um_realm_manager_new (NULL, on_realm_manager_created, page);
}
static void
on_realmd_disappeared (GDBusConnection *unused1,
const gchar *unused2,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
if (page->realm_manager != NULL) {
g_signal_handlers_disconnect_by_func (page->realm_manager,
on_manager_realm_added,
page);
g_clear_object (&page->realm_manager);
}
gtk_widget_set_visible (GTK_WIDGET (page), FALSE);
}
static void
on_domain_changed (GtkComboBox *widget,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
page->domain_chosen = TRUE;
validation_changed (page);
clear_entry_validation_error (GTK_ENTRY (gtk_combo_box_get_child (widget)));
}
static void
on_entry_changed (GtkEditable *editable,
gpointer user_data)
{
GisAccountPageEnterprise *page = user_data;
validation_changed (page);
clear_entry_validation_error (GTK_ENTRY (editable));
}
static void
on_password_changed (GtkEditable *editable,
gpointer user_data)
{
clear_password_validation_error (GTK_WIDGET (editable));
}
static void
gis_account_page_enterprise_realize (GtkWidget *widget)
{
GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (widget);
GtkWidget *gis_page;
gis_page = gtk_widget_get_ancestor (widget, GIS_TYPE_PAGE);
g_object_bind_property (gis_page, "small-screen",
page->header, "show-icon",
G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
GTK_WIDGET_CLASS (gis_account_page_enterprise_parent_class)->realize (widget);
}
static void
gis_account_page_enterprise_constructed (GObject *object)
{
GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object);
G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->constructed (object);
page->act_client = act_user_manager_get_default ();
page->realmd_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM, "org.freedesktop.realmd",
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
on_realmd_appeared, on_realmd_disappeared,
page, NULL);
g_signal_connect (page->join_dialog, "response",
G_CALLBACK (on_join_response), page);
g_signal_connect (page->domain, "changed",
G_CALLBACK (on_domain_changed), page);
g_signal_connect (page->login, "changed",
G_CALLBACK (on_entry_changed), page);
g_signal_connect (page->password, "changed",
G_CALLBACK (on_password_changed), page);
g_signal_connect (page->join_password, "changed",
G_CALLBACK (on_password_changed), page);
}
static void
gis_account_page_enterprise_dispose (GObject *object)
{
GisAccountPageEnterprise *page = GIS_ACCOUNT_PAGE_ENTERPRISE (object);
if (page->realmd_watch)
g_bus_unwatch_name (page->realmd_watch);
page->realmd_watch = 0;
g_cancellable_cancel (page->cancellable);
g_clear_object (&page->realm_manager);
g_clear_object (&page->realm);
g_clear_object (&page->cancellable);
G_OBJECT_CLASS (gis_account_page_enterprise_parent_class)->dispose (object);
}
static void
gis_account_page_enterprise_class_init (GisAccountPageEnterpriseClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->constructed = gis_account_page_enterprise_constructed;
object_class->dispose = gis_account_page_enterprise_dispose;
widget_class->realize = gis_account_page_enterprise_realize;
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-enterprise.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, login);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, password);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, domain);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, realms_model);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, header);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_dialog);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_name);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_password);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_domain);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageEnterprise, join_computer);
signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[USER_CACHED] = g_signal_new ("user-cached", GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
}
static void
gis_account_page_enterprise_init (GisAccountPageEnterprise *page)
{
g_type_ensure (GIS_TYPE_PAGE_HEADER);
gtk_widget_init_template (GTK_WIDGET (page));
page->domain_entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->domain));
}
void
gis_account_page_enterprise_shown (GisAccountPageEnterprise *page)
{
gtk_widget_grab_focus (page->domain_entry);
}

View file

@ -0,0 +1,42 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#pragma once
#include <adwaita.h>
/* For GisPageApplyCallback */
#include "gis-page.h"
G_BEGIN_DECLS
#define GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE (gis_account_page_enterprise_get_type ())
G_DECLARE_FINAL_TYPE (GisAccountPageEnterprise, gis_account_page_enterprise, GIS, ACCOUNT_PAGE_ENTERPRISE, AdwBin)
gboolean gis_account_page_enterprise_validate (GisAccountPageEnterprise *enterprise);
gboolean gis_account_page_enterprise_apply (GisAccountPageEnterprise *enterprise,
GCancellable *cancellable,
GisPageApplyCallback callback,
gpointer data);
void gis_account_page_enterprise_shown (GisAccountPageEnterprise *enterprise);
G_END_DECLS

View file

@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GisAccountPageEnterprise" parent="AdwBin">
<child>
<object class="GtkBox" id="area">
<property name="orientation">vertical</property>
<property name="valign">fill</property>
<child>
<object class="GisPageHeader" id="header">
<property name="margin_bottom">26</property>
<property name="margin_top">24</property>
<property name="title" translatable="yes">Enterprise Login</property>
<property name="subtitle" translatable="yes">Enterprise login allows an existing centrally managed user account to be used on this device. You can also use this account to access company resources on the internet.</property>
<property name="icon_name">dialog-password-symbolic</property>
<property name="show_icon">True</property>
</object>
</child>
<child>
<object class="GtkGrid" id="form">
<property name="row_spacing">12</property>
<property name="column_spacing">12</property>
<property name="margin_bottom">32</property>
<child>
<object class="GtkLabel" id="label4">
<property name="halign">end</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">_Domain</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">domain</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="halign">end</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">_Username</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">login</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label9">
<property name="halign">end</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">_Password</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">password</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="login">
<property name="hexpand">True</property>
<property name="max-length">255</property>
<property name="width-chars">25</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkPasswordEntry" id="password">
<property name="hexpand">True</property>
<property name="width-chars">25</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkComboBox" id="domain">
<property name="hexpand">True</property>
<property name="model">realms_model</property>
<property name="has_entry">True</property>
<property name="entry_text_column">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="margin_bottom">12</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Enterprise domain or realm name</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
<attributes>
<attribute name="scale" value="0.8"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel" id="filler">
<layout>
<property name="column">2</property>
<property name="row">4</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<object class="GtkDialog" id="join_dialog">
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<property name="title" translatable="yes">Domain Administrator Login</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog-vbox1">
<property name="orientation">vertical</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<property name="margin-start">18</property>
<property name="margin-end">18</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="label12">
<property name="xalign">0.5</property>
<property name="yalign">0</property>
<property name="wrap">True</property>
<property name="max-width-chars">60</property>
<property name="label" translatable="yes">In order to use enterprise logins, this computer needs to be enrolled in a domain. Please have your network administrator type the domain password here, and choose a unique computer name for your computer.</property>
</object>
</child>
<child>
<object class="GtkGrid" id="grid1">
<property name="margin-start">12</property>
<property name="hexpand">True</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="label13">
<property name="xalign">1</property>
<property name="label" translatable="yes">_Domain</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">join_domain</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="join_domain">
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="xalign">1</property>
<property name="label" translatable="yes">_Computer</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">join_computer</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkEntry" id="join_computer">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label14">
<property name="xalign">1</property>
<property name="label" translatable="yes">Administrator _Name</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">join_name</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkEntry" id="join_name">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="label15">
<property name="xalign">1</property>
<property name="label" translatable="yes">Administrator Password</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">join_password</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkPasswordEntry" id="join_password">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="action">
<object class="GtkButton" id="button_cancel">
<property name="label" translatable="yes">_Cancel</property>
<property name="use_underline">True</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="button_ok">
<property name="label" translatable="yes">C_ontinue</property>
<property name="use_underline">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<action-widgets>
<action-widget response="-5" default="true">button_ok</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="realms_model">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
<!-- column-name gobject -->
<column type="GObject"/>
</columns>
</object>
</interface>

View file

@ -0,0 +1,644 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include "gis-page.h"
#include "gis-account-page-local.h"
#include "gnome-initial-setup.h"
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <string.h>
#include <act/act-user-manager.h>
#include "um-utils.h"
#include "um-photo-dialog.h"
#include "gis-page-header.h"
#include <json-glib/json-glib.h>
/* Key and values that are written as metadata to the exported user avatar this
* way it's possible to know how the image was initially created.
* If set to generated we can regenerated the avatar when the style changes or
* when the users full name changes. The other two values don't have a specific use yet */
#define IMAGE_SOURCE_KEY "tEXt::source"
#define IMAGE_SOURCE_VALUE_GENERATED "gnome-generated"
#define IMAGE_SOURCE_VALUE_FACE "gnome-face"
#define IMAGE_SOURCE_VALUE_CUSTOM "gnome-custom"
#define IMAGE_SIZE 512
#define VALIDATION_TIMEOUT 600
struct _GisAccountPageLocal
{
AdwBin parent;
GtkWidget *avatar_button;
GtkWidget *remove_avatar_button;
GtkWidget *avatar_image;
GtkWidget *header;
GtkWidget *fullname_entry;
GtkWidget *username_combo;
GtkWidget *enable_parental_controls_box;
GtkWidget *enable_parental_controls_check_button;
gboolean has_custom_username;
GtkWidget *username_explanation;
UmPhotoDialog *photo_dialog;
gint timeout_id;
ActUserManager *act_client;
gboolean valid_name;
gboolean valid_username;
ActUserAccountType account_type;
};
G_DEFINE_TYPE (GisAccountPageLocal, gis_account_page_local, ADW_TYPE_BIN);
enum {
VALIDATION_CHANGED,
MAIN_USER_CREATED,
PARENT_USER_CREATED,
CONFIRM,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
validation_changed (GisAccountPageLocal *page)
{
g_signal_emit (page, signals[VALIDATION_CHANGED], 0);
}
static void
update_avatar_text (GisAccountPageLocal *page)
{
const gchar *name;
name = gtk_editable_get_text (GTK_EDITABLE (page->fullname_entry));
if (*name == '\0') {
GtkWidget *entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo));
name = gtk_editable_get_text (GTK_EDITABLE (entry));
}
if (*name == '\0') {
name = NULL;
}
adw_avatar_set_text (ADW_AVATAR (page->avatar_image), name);
}
static gboolean
validate (GisAccountPageLocal *page)
{
GtkWidget *entry;
const gchar *name, *username;
gboolean parental_controls_enabled;
gchar *tip;
g_clear_handle_id (&page->timeout_id, g_source_remove);
entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo));
name = gtk_editable_get_text (GTK_EDITABLE (page->fullname_entry));
username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (page->username_combo));
#ifdef HAVE_PARENTAL_CONTROLS
parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (page->enable_parental_controls_check_button));
#else
parental_controls_enabled = FALSE;
#endif
page->valid_name = is_valid_name (name);
if (page->valid_name)
set_entry_validation_checkmark (GTK_ENTRY (page->fullname_entry));
page->valid_username = is_valid_username (username, parental_controls_enabled, &tip);
if (page->valid_username)
set_entry_validation_checkmark (GTK_ENTRY (entry));
gtk_label_set_text (GTK_LABEL (page->username_explanation), tip);
g_free (tip);
validation_changed (page);
return G_SOURCE_REMOVE;
}
static gboolean
on_focusout (GisAccountPageLocal *page)
{
validate (page);
return FALSE;
}
static void
fullname_changed (GtkWidget *w,
GParamSpec *pspec,
GisAccountPageLocal *page)
{
GtkWidget *entry;
GtkTreeModel *model;
const char *name;
name = gtk_editable_get_text (GTK_EDITABLE (w));
entry = gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo));
model = gtk_combo_box_get_model (GTK_COMBO_BOX (page->username_combo));
gtk_list_store_clear (GTK_LIST_STORE (model));
if ((name == NULL || strlen (name) == 0) && !page->has_custom_username) {
gtk_editable_set_text (GTK_EDITABLE (entry), "");
}
else if (name != NULL && strlen (name) != 0) {
generate_username_choices (name, GTK_LIST_STORE (model));
if (!page->has_custom_username)
gtk_combo_box_set_active (GTK_COMBO_BOX (page->username_combo), 0);
}
clear_entry_validation_error (GTK_ENTRY (w));
page->valid_name = FALSE;
/* username_changed() is called consequently due to changes */
update_avatar_text (page);
}
static void
username_changed (GtkComboBoxText *combo,
GisAccountPageLocal *page)
{
GtkWidget *entry;
const gchar *username;
entry = gtk_combo_box_get_child (GTK_COMBO_BOX (combo));
username = gtk_editable_get_text (GTK_EDITABLE (entry));
if (*username == '\0')
page->has_custom_username = FALSE;
else if (gtk_widget_has_focus (entry) ||
gtk_widget_get_focus_child (entry) ||
gtk_combo_box_get_active (GTK_COMBO_BOX (page->username_combo)) > 0)
page->has_custom_username = TRUE;
clear_entry_validation_error (GTK_ENTRY (entry));
page->valid_username = FALSE;
validation_changed (page);
if (page->timeout_id != 0)
g_source_remove (page->timeout_id);
page->timeout_id = g_timeout_add (VALIDATION_TIMEOUT, (GSourceFunc)validate, page);
update_avatar_text (page);
}
static void
on_remove_avatar_button_clicked (GisAccountPageLocal *page)
{
adw_avatar_set_custom_image (ADW_AVATAR (page->avatar_image), NULL);
gtk_widget_set_visible (GTK_WIDGET (page->remove_avatar_button), FALSE);
}
static void
avatar_callback (const gchar *filename,
gpointer user_data)
{
GisAccountPageLocal *page = user_data;
g_autoptr(GdkTexture) texture = NULL;
if (filename) {
g_autoptr(GError) error = NULL;
texture = gdk_texture_new_from_filename (filename, &error);
if (error)
g_warning ("Failed to load user icon from path %s: %s", filename, error->message);
}
adw_avatar_set_custom_image (ADW_AVATAR (page->avatar_image), GDK_PAINTABLE (texture));
gtk_widget_set_visible (GTK_WIDGET (page->remove_avatar_button), texture != NULL);
}
static void
confirm (GisAccountPageLocal *page)
{
if (gis_account_page_local_validate (page))
g_signal_emit (page, signals[CONFIRM], 0);
}
static void
enable_parental_controls_check_button_toggled_cb (GtkCheckButton *check_button,
gpointer user_data)
{
GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (user_data);
gboolean parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (page->enable_parental_controls_check_button));
/* This sets the account type of the main user. When we save_data(), we create
* two users if parental controls are enabled: the first user is always an
* admin, and the second user is the main user using this @account_type. */
page->account_type = parental_controls_enabled ? ACT_USER_ACCOUNT_TYPE_STANDARD : ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
validate (page);
}
static void
track_focus_out (GisAccountPageLocal *page,
GtkWidget *widget)
{
GtkEventController *focus_controller;
focus_controller = gtk_event_controller_focus_new ();
gtk_widget_add_controller (widget, focus_controller);
g_signal_connect_swapped (focus_controller, "leave", G_CALLBACK (on_focusout), page);
}
static void
gis_account_page_local_constructed (GObject *object)
{
GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object);
G_OBJECT_CLASS (gis_account_page_local_parent_class)->constructed (object);
page->act_client = act_user_manager_get_default ();
page->photo_dialog = um_photo_dialog_new (avatar_callback, page);
g_signal_connect (page->fullname_entry, "notify::text",
G_CALLBACK (fullname_changed), page);
track_focus_out (page, page->fullname_entry);
g_signal_connect_swapped (page->fullname_entry, "activate",
G_CALLBACK (validate), page);
g_signal_connect (page->username_combo, "changed",
G_CALLBACK (username_changed), page);
track_focus_out (page, page->username_combo);
g_signal_connect_swapped (gtk_combo_box_get_child (GTK_COMBO_BOX (page->username_combo)),
"activate", G_CALLBACK (confirm), page);
g_signal_connect_swapped (page->fullname_entry, "activate",
G_CALLBACK (confirm), page);
g_signal_connect (page->enable_parental_controls_check_button, "toggled",
G_CALLBACK (enable_parental_controls_check_button_toggled_cb), page);
/* Disable parental controls if support is not compiled in. */
#ifndef HAVE_PARENTAL_CONTROLS
gtk_widget_set_visible (page->enable_parental_controls_box, FALSE);
#endif
page->valid_name = FALSE;
page->valid_username = FALSE;
/* FIXME: change this for a large deployment scenario; maybe through a GSetting? */
page->account_type = ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR;
g_object_set (page->header, "subtitle", _("We need a few details to complete setup."), NULL);
gtk_editable_set_text (GTK_EDITABLE (page->fullname_entry), "");
gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (page->username_combo))));
page->has_custom_username = FALSE;
gtk_menu_button_set_popover (GTK_MENU_BUTTON (page->avatar_button),
GTK_WIDGET (page->photo_dialog));
validate (page);
}
static void
gis_account_page_local_dispose (GObject *object)
{
GisAccountPageLocal *page = GIS_ACCOUNT_PAGE_LOCAL (object);
g_clear_handle_id (&page->timeout_id, g_source_remove);
G_OBJECT_CLASS (gis_account_page_local_parent_class)->dispose (object);
}
/* This function was taken from AdwAvatar and modified so that it's possible to
* export a GdkTexture at a different size than the AdwAvatar is rendered
* See: https://gitlab.gnome.org/GNOME/libadwaita/-/blob/afd0fab86ff9b4332d165b985a435ea6f822d41b/src/adw-avatar.c#L751
* License: LGPL-2.1-or-later */
static GdkTexture *
draw_avatar_to_texture (AdwAvatar *avatar, int size)
{
GdkTexture *result;
GskRenderNode *node;
GtkSnapshot *snapshot;
GdkPaintable *paintable;
GtkNative *native;
GskRenderer *renderer;
int real_size;
graphene_matrix_t transform;
gboolean transform_ok;
real_size = adw_avatar_get_size (avatar);
/* This works around the issue that when the custom-image or text of the AdwAvatar changes the
* allocation gets invalidateds and therefore we can't snapshot the widget till the allocation
* is recalculated */
gtk_widget_measure (GTK_WIDGET (avatar), GTK_ORIENTATION_HORIZONTAL, real_size, NULL, NULL, NULL, NULL);
gtk_widget_allocate (GTK_WIDGET (avatar), real_size, real_size, -1, NULL);
transform_ok = gtk_widget_compute_transform (GTK_WIDGET (avatar),
gtk_widget_get_first_child (GTK_WIDGET (avatar)),
&transform);
g_assert (transform_ok);
snapshot = gtk_snapshot_new ();
gtk_snapshot_transform_matrix (snapshot, &transform);
GTK_WIDGET_GET_CLASS (avatar)->snapshot (GTK_WIDGET (avatar), snapshot);
/* Create first a GdkPaintable at the size the avatar was drawn
* then create a GdkSnapshot of it at the size requested */
paintable = gtk_snapshot_free_to_paintable (snapshot, &GRAPHENE_SIZE_INIT (real_size, real_size));
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (paintable, snapshot, size, size);
g_object_unref (paintable);
node = gtk_snapshot_free_to_node (snapshot);
native = gtk_widget_get_native (GTK_WIDGET (avatar));
renderer = gtk_native_get_renderer (native);
result = gsk_renderer_render_texture (renderer, node, &GRAPHENE_RECT_INIT (-1, 0, size, size));
gsk_render_node_unref (node);
return result;
}
static GdkPixbuf *
texture_to_pixbuf (GdkTexture *texture)
{
g_autoptr(GdkTextureDownloader) downloader = NULL;
g_autoptr(GBytes) bytes = NULL;
gsize stride;
downloader = gdk_texture_downloader_new (texture);
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R8G8B8A8);
bytes = gdk_texture_downloader_download_bytes (downloader, &stride);
return gdk_pixbuf_new_from_bytes (bytes,
GDK_COLORSPACE_RGB,
true,
8,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
stride);
}
static void
set_user_avatar (GisPage *page,
ActUser *user)
{
GdkTexture *texture = NULL;
g_autoptr(GError) error = NULL;
g_autofree gchar *path = NULL;
const gchar *image_source;
int fd;
texture = gis_driver_get_avatar (page->driver);
if (gis_driver_get_has_default_avatar (page->driver))
image_source = IMAGE_SOURCE_VALUE_GENERATED;
else
image_source = IMAGE_SOURCE_VALUE_FACE;
/* IMAGE_SOURCE_VALUE_CUSTOM isn't used here since we don't allow custom files
* to be set during initial setup */
fd = g_file_open_tmp ("usericonXXXXXX", &path, &error);
if (fd == -1) {
g_warning ("Failed to create temporary user icon: %s", error->message);
return;
}
g_autoptr(GdkPixbuf) pixbuf = texture_to_pixbuf (texture);
if (!gdk_pixbuf_save (pixbuf, path, "png", &error, IMAGE_SOURCE_KEY, image_source, NULL)) {
g_warning ("Failed to save temporary user icon: %s", error->message);
}
close (fd);
act_user_set_icon_file (user, path);
g_remove (path);
}
static gboolean
local_create_user (GisAccountPageLocal *local,
GisPage *page,
GError **error)
{
const gchar *username;
const gchar *fullname;
gboolean parental_controls_enabled;
g_autoptr(ActUser) main_user = NULL;
g_autoptr(ActUser) parent_user = NULL;
username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (local->username_combo));
fullname = gtk_editable_get_text (GTK_EDITABLE (local->fullname_entry));
parental_controls_enabled = gis_driver_get_parental_controls_enabled (page->driver);
/* Always create the admin user first, in case of failure part-way through
* this function, which would leave us with no admin user at all. */
if (parental_controls_enabled) {
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusConnection) connection = NULL;
const gchar *parent_username = "administrator";
const gchar *parent_fullname = _("Administrator");
parent_user = act_user_manager_create_user (local->act_client, parent_username, parent_fullname, ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR, error);
if (parent_user == NULL)
{
g_prefix_error (error,
_("Failed to create user '%s': "),
parent_username);
return FALSE;
}
/* Make the admin account usable in case g-i-s crashes. If all goes
* according to plan a password will be set on it in gis-password-page.c */
act_user_set_password_mode (parent_user, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN);
/* Mark it as the parent user account.
* FIXME: This should be async. */
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error);
if (connection != NULL) {
g_dbus_connection_call_sync (connection,
"org.freedesktop.Accounts",
act_user_get_object_path (parent_user),
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new ("(ssv)",
"com.endlessm.ParentalControls.AccountInfo",
"IsParent",
g_variant_new_boolean (TRUE)),
NULL, /* reply type */
G_DBUS_CALL_FLAGS_NONE,
-1, /* default timeout */
NULL, /* cancellable */
&local_error);
}
if (local_error != NULL) {
/* Make this non-fatal, since the correct accounts-service interface
* might not be installed, depending on which version of malcontent is installed. */
g_warning ("Failed to mark user as parent: %s", local_error->message);
g_clear_error (&local_error);
}
g_signal_emit (local, signals[PARENT_USER_CREATED], 0, parent_user, "");
}
/* Now create the main user. */
main_user = act_user_manager_create_user (local->act_client, username, fullname, local->account_type, error);
if (main_user == NULL)
{
g_prefix_error (error,
_("Failed to create user '%s': "),
username);
/* FIXME: Could we delete the @parent_user at this point to reset the state
* and allow g-i-s to be run again after a reboot? */
return FALSE;
}
set_user_avatar (page, main_user);
g_signal_emit (local, signals[MAIN_USER_CREATED], 0, main_user, "");
return TRUE;
}
static void
gis_account_page_local_class_init (GisAccountPageLocalClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page-local.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_button);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, remove_avatar_button);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, avatar_image);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, header);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, fullname_entry);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_combo);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, username_explanation);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_box);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPageLocal, enable_parental_controls_check_button);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), on_remove_avatar_button_clicked);
object_class->constructed = gis_account_page_local_constructed;
object_class->dispose = gis_account_page_local_dispose;
signals[VALIDATION_CHANGED] = g_signal_new ("validation-changed", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[MAIN_USER_CREATED] = g_signal_new ("main-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
signals[PARENT_USER_CREATED] = g_signal_new ("parent-user-created", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, ACT_TYPE_USER, G_TYPE_STRING);
signals[CONFIRM] = g_signal_new ("confirm", GIS_TYPE_ACCOUNT_PAGE_LOCAL,
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
gis_account_page_local_init (GisAccountPageLocal *page)
{
g_type_ensure (GIS_TYPE_PAGE_HEADER);
gtk_widget_init_template (GTK_WIDGET (page));
}
gboolean
gis_account_page_local_validate (GisAccountPageLocal *page)
{
return page->valid_name && page->valid_username;
}
gboolean
gis_account_page_local_create_user (GisAccountPageLocal *local,
GisPage *page,
GError **error)
{
return local_create_user (local, page, error);
}
gboolean
gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page)
{
GisDriver *driver = GIS_PAGE (page)->driver;
const gchar *username, *full_name;
gboolean parental_controls_enabled;
GdkTexture *texture = NULL;
g_object_freeze_notify (G_OBJECT (driver));
username = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (local->username_combo));
gis_driver_set_username (driver, username);
full_name = gtk_editable_get_text (GTK_EDITABLE (local->fullname_entry));
gis_driver_set_full_name (driver, full_name);
texture = GDK_TEXTURE (adw_avatar_get_custom_image (ADW_AVATAR (local->avatar_image)));
if (texture)
{
gis_driver_set_avatar (driver, texture);
gis_driver_set_has_default_avatar (driver, FALSE);
}
else
{
texture = draw_avatar_to_texture (ADW_AVATAR (local->avatar_image), IMAGE_SIZE);
gis_driver_set_avatar (driver, texture);
gis_driver_set_has_default_avatar (driver, TRUE);
g_object_unref (texture);
}
#ifdef HAVE_PARENTAL_CONTROLS
parental_controls_enabled = gtk_check_button_get_active (GTK_CHECK_BUTTON (local->enable_parental_controls_check_button));
#else
parental_controls_enabled = FALSE;
#endif
gis_driver_set_parental_controls_enabled (driver, parental_controls_enabled);
g_object_thaw_notify (G_OBJECT (driver));
return FALSE;
}
void
gis_account_page_local_shown (GisAccountPageLocal *local)
{
gtk_widget_grab_focus (local->fullname_entry);
}

View file

@ -0,0 +1,39 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#pragma once
#include <adwaita.h>
G_BEGIN_DECLS
#define GIS_TYPE_ACCOUNT_PAGE_LOCAL (gis_account_page_local_get_type ())
G_DECLARE_FINAL_TYPE (GisAccountPageLocal, gis_account_page_local, GIS, ACCOUNT_PAGE_LOCAL, AdwBin)
gboolean gis_account_page_local_validate (GisAccountPageLocal *local);
gboolean gis_account_page_local_apply (GisAccountPageLocal *local, GisPage *page);
gboolean gis_account_page_local_create_user (GisAccountPageLocal *local,
GisPage *page,
GError **error);
void gis_account_page_local_shown (GisAccountPageLocal *local);
G_END_DECLS

View file

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GisAccountPageLocal" parent="AdwBin">
<child>
<object class="GtkBox" id="area">
<property name="halign">center</property>
<property name="valign">fill</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkOverlay">
<property name="margin_top">24</property>
<property name="halign">center</property>
<child>
<object class="AdwAvatar" id="avatar_image">
<property name="size">128</property>
<property name="show-initials">True</property>
</object>
</child>
<child type="overlay">
<object class="AdwBin">
<style>
<class name="cutout-button"/>
</style>
<property name="halign">end</property>
<property name="valign">end</property>
<child>
<object class="GtkMenuButton" id="avatar_button">
<property name="tooltip-text" translatable="yes">Change Avatar</property>
<property name="icon-name">document-edit-symbolic</property>
<style>
<class name="circular"/>
</style>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="AdwBin">
<style>
<class name="cutout-button"/>
</style>
<property name="halign">end</property>
<property name="valign">start</property>
<child>
<object class="GtkButton" id="remove_avatar_button">
<property name="tooltip-text" translatable="yes">Remove Avatar</property>
<property name="visible">False</property>
<property name="icon-name">user-trash-symbolic</property>
<signal name="clicked" handler="on_remove_avatar_button_clicked" object="GisAccountPageLocal" swapped="yes"/>
<style>
<class name="circular"/>
<class name="destructive-image-button"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GisPageHeader" id="header">
<property name="show-icon">False</property>
<property name="margin_top">18</property>
<property name="title" translatable="yes">About You</property>
<property name="subtitle" translatable="yes">Please provide a name and username. You can choose a picture too.</property>
</object>
</child>
<child>
<object class="GtkGrid" id="form">
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<property name="margin_top">42</property>
<child>
<object class="GtkLabel" id="fullname_label">
<property name="halign">end</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">_Full Name</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">fullname_entry</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="fullname_entry">
<property name="max_length">255</property>
<property name="width-chars">25</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="username_label">
<property name="halign">end</property>
<property name="xalign">1</property>
<property name="label" translatable="yes">_Username</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">username_combo</property>
<property name="margin_top">6</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkComboBoxText" id="username_combo">
<property name="has_entry">True</property>
<property name="entry_text_column">0</property>
<property name="id_column">1</property>
<property name="margin_top">6</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="username_explanation">
<property name="yalign">0</property>
<property name="xalign">0</property>
<property name="width-chars">35</property>
<property name="max-width-chars">35</property>
<property name="height-request">50</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
</layout>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="0.8"/>
</attributes>
</object>
</child>
<child>
<object class="GtkBox" id="enable_parental_controls_box">
<property name="orientation">vertical</property>
<layout>
<property name="column">1</property>
<property name="row">6</property>
</layout>
<child>
<object class="GtkCheckButton" id="enable_parental_controls_check_button">
<property name="label" translatable="yes">Set up _parental controls for this user</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">For use by a parent or supervisor, who must set up their own password.</property>
<property name="yalign">0</property>
<property name="xalign">0</property>
<property name="margin-start">24</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="0.8"/>
</attributes>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,310 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
/* Account page {{{1 */
#define PAGE_ID "account"
#include "config.h"
#include "account-resources.h"
#include "gis-account-page.h"
#include "gis-account-page-local.h"
#include "gis-account-page-enterprise.h"
#include <glib/gi18n.h>
#include <gio/gio.h>
struct _GisAccountPage
{
GisPage parent;
GtkWidget *page_local;
GtkWidget *page_enterprise;
GtkWidget *stack;
GtkWidget *page_toggle;
GtkWidget *offline_label;
GtkWidget *offline_stack;
UmAccountMode mode;
};
G_DEFINE_TYPE (GisAccountPage, gis_account_page, GIS_TYPE_PAGE);
static void
enterprise_apply_complete (GisPage *dummy,
gboolean valid,
gpointer user_data)
{
GisAccountPage *page = GIS_ACCOUNT_PAGE (user_data);
gis_driver_set_username (GIS_PAGE (page)->driver, NULL);
gis_page_apply_complete (GIS_PAGE (page), valid);
}
static gboolean
page_validate (GisAccountPage *page)
{
switch (page->mode) {
case UM_LOCAL:
return gis_account_page_local_validate (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
case UM_ENTERPRISE:
return gis_account_page_enterprise_validate (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise));
default:
g_assert_not_reached ();
}
}
static void
update_page_validation (GisAccountPage *page)
{
gis_page_set_complete (GIS_PAGE (page), page_validate (page));
}
static void
on_validation_changed (gpointer page_area,
GisAccountPage *page)
{
update_page_validation (page);
}
static void
set_mode (GisAccountPage *page,
UmAccountMode mode)
{
if (page->mode == mode)
return;
page->mode = mode;
gis_driver_set_account_mode (GIS_PAGE (page)->driver, mode);
switch (mode)
{
case UM_LOCAL:
gtk_stack_set_visible_child (GTK_STACK (page->stack), page->page_local);
gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
break;
case UM_ENTERPRISE:
gtk_stack_set_visible_child (GTK_STACK (page->stack), page->page_enterprise);
gis_account_page_enterprise_shown (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise));
break;
default:
g_assert_not_reached ();
}
update_page_validation (page);
}
static void
toggle_mode (GtkToggleButton *button,
gpointer user_data)
{
set_mode (GIS_ACCOUNT_PAGE (user_data),
gtk_toggle_button_get_active (button) ? UM_ENTERPRISE : UM_LOCAL);
}
static gboolean
gis_account_page_apply (GisPage *gis_page,
GCancellable *cancellable)
{
GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
switch (page->mode) {
case UM_LOCAL:
return gis_account_page_local_apply (GIS_ACCOUNT_PAGE_LOCAL (page->page_local), gis_page);
case UM_ENTERPRISE:
return gis_account_page_enterprise_apply (GIS_ACCOUNT_PAGE_ENTERPRISE (page->page_enterprise), cancellable,
enterprise_apply_complete, page);
default:
g_assert_not_reached ();
break;
}
}
static gboolean
gis_account_page_save_data (GisPage *gis_page,
GError **error)
{
GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
switch (page->mode) {
case UM_LOCAL:
return gis_account_page_local_create_user (GIS_ACCOUNT_PAGE_LOCAL (page->page_local), gis_page, error);
case UM_ENTERPRISE:
/* Nothing to do. */
return TRUE;
default:
g_assert_not_reached ();
return FALSE;
}
}
static void
gis_account_page_shown (GisPage *gis_page)
{
GisAccountPage *page = GIS_ACCOUNT_PAGE (gis_page);
gis_account_page_local_shown (GIS_ACCOUNT_PAGE_LOCAL (page->page_local));
}
static void
on_local_main_user_created (GtkWidget *page_local,
ActUser *user,
const gchar *password,
GisAccountPage *page)
{
const gchar *language;
language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
if (language)
act_user_set_language (user, language);
gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password);
}
static void
on_local_parent_user_created (GtkWidget *page_local,
ActUser *user,
const gchar *password,
GisAccountPage *page)
{
const gchar *language;
language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
if (language)
act_user_set_language (user, language);
gis_driver_set_parent_permissions (GIS_PAGE (page)->driver, user, password);
}
static void
on_local_page_confirmed (GisAccountPageLocal *local,
GisAccountPage *page)
{
gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver));
}
static void
on_local_user_cached (GtkWidget *page_local,
ActUser *user,
char *password,
GisAccountPage *page)
{
const gchar *language;
language = gis_driver_get_user_language (GIS_PAGE (page)->driver);
if (language)
act_user_set_language (user, language);
gis_driver_set_user_permissions (GIS_PAGE (page)->driver, user, password);
}
static void
on_network_changed (GNetworkMonitor *monitor,
gboolean available,
GisAccountPage *page)
{
if (!available && page->mode != UM_ENTERPRISE)
gtk_stack_set_visible_child (GTK_STACK (page->offline_stack), page->offline_label);
else
gtk_stack_set_visible_child (GTK_STACK (page->offline_stack), page->page_toggle);
}
static void
gis_account_page_constructed (GObject *object)
{
GisAccountPage *page = GIS_ACCOUNT_PAGE (object);
GNetworkMonitor *monitor;
gboolean available;
G_OBJECT_CLASS (gis_account_page_parent_class)->constructed (object);
g_signal_connect (page->page_local, "validation-changed",
G_CALLBACK (on_validation_changed), page);
g_signal_connect (page->page_local, "main-user-created",
G_CALLBACK (on_local_main_user_created), page);
g_signal_connect (page->page_local, "parent-user-created",
G_CALLBACK (on_local_parent_user_created), page);
g_signal_connect (page->page_local, "confirm",
G_CALLBACK (on_local_page_confirmed), page);
g_signal_connect (page->page_enterprise, "validation-changed",
G_CALLBACK (on_validation_changed), page);
g_signal_connect (page->page_enterprise, "user-cached",
G_CALLBACK (on_local_user_cached), page);
update_page_validation (page);
g_signal_connect (page->page_toggle, "toggled", G_CALLBACK (toggle_mode), page);
g_object_bind_property (page, "applying", page->page_toggle, "sensitive", G_BINDING_INVERT_BOOLEAN);
g_object_bind_property (page->page_enterprise, "visible", page->offline_stack, "visible", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
/* force a refresh by setting to an invalid value */
page->mode = NUM_MODES;
set_mode (page, UM_LOCAL);
monitor = g_network_monitor_get_default ();
available = g_network_monitor_get_network_available (monitor);
on_network_changed (monitor, available, page);
g_signal_connect_object (monitor, "network-changed", G_CALLBACK (on_network_changed), page, 0);
gtk_widget_set_visible (GTK_WIDGET (page), TRUE);
}
static void
gis_account_page_locale_changed (GisPage *page)
{
gis_page_set_title (GIS_PAGE (page), _("About You"));
}
static void
gis_account_page_class_init (GisAccountPageClass *klass)
{
GisPageClass *page_class = GIS_PAGE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-account-page.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_local);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_enterprise);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, stack);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, page_toggle);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_label);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GisAccountPage, offline_stack);
page_class->page_id = PAGE_ID;
page_class->locale_changed = gis_account_page_locale_changed;
page_class->apply = gis_account_page_apply;
page_class->save_data = gis_account_page_save_data;
page_class->shown = gis_account_page_shown;
object_class->constructed = gis_account_page_constructed;
gis_add_style_from_resource ("/org/gnome/initial-setup/gis-account-page.css");
}
static void
gis_account_page_init (GisAccountPage *page)
{
g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_LOCAL);
g_type_ensure (GIS_TYPE_ACCOUNT_PAGE_ENTERPRISE);
gtk_widget_init_template (GTK_WIDGET (page));
}

View file

@ -0,0 +1,10 @@
.cutout-button {
background-color: @window_bg_color;
border-radius: 9999px;
padding: 2px;
}
/* This is used for remove_avatar_button */
.destructive-image-button {
color: @destructive_color;
}

View file

@ -0,0 +1,33 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#pragma once
#include <glib-object.h>
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
#define GIS_TYPE_ACCOUNT_PAGE (gis_account_page_get_type ())
G_DECLARE_FINAL_TYPE (GisAccountPage, gis_account_page, GIS, ACCOUNT_PAGE, GisPage)
G_END_DECLS

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GisAccountPage" parent="GisPage">
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkStack" id="stack">
<property name="valign">start</property>
<property name="vexpand">True</property>
<child>
<object class="GisAccountPageLocal" id="page_local" />
</child>
<child>
<object class="GisAccountPageEnterprise" id="page_enterprise" />
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkStack" id="offline_stack">
<property name="valign">end</property>
<child>
<object class="GtkToggleButton" id="page_toggle">
<property name="use_underline">True</property>
<property name="label" translatable="yes">_Enterprise Login</property>
<property name="halign">center</property>
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkLabel" id="offline_label">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Go online to set up Enterprise Login.</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,32 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include "gis-account-pages.h"
#include "gis-account-page.h"
GisPage *
gis_prepare_account_page (GisDriver *driver)
{
return g_object_new (GIS_TYPE_ACCOUNT_PAGE,
"driver", driver,
NULL);
}

View file

@ -0,0 +1,33 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#pragma once
#include <glib-object.h>
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
GisPage *gis_prepare_account_page (GisDriver *driver);
G_END_DECLS

View file

@ -0,0 +1,33 @@
realmd_namespace = 'org.freedesktop.realmd'
sources += gnome.gdbus_codegen(
'um-realm-generated',
realmd_namespace + '.xml',
interface_prefix: realmd_namespace + '.',
namespace: 'UmRealm',
object_manager: true,
annotations: ['org.freedesktop.realmd.Realm', 'org.gtk.GDBus.C.Name', 'Common']
)
sources += gnome.compile_resources(
'account-resources',
files('account.gresource.xml'),
c_name: 'account'
)
sources += files(
'gis-account-page.c',
'gis-account-page.h',
'gis-account-pages.c',
'gis-account-pages.h',
'gis-account-page-local.c',
'gis-account-page-local.h',
'gis-account-page-enterprise.c',
'gis-account-page-enterprise.h',
'um-realm-manager.c',
'um-realm-manager.h',
'um-utils.c',
'um-photo-dialog.c',
'um-photo-dialog.h'
)
account_sources_dir = meson.current_source_dir()

View file

@ -0,0 +1,666 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/">
<!--
org.freedesktop.realmd.Provider:
@short_description: a realm provider
Various realm providers represent different software implementations
that provide access to realms or domains.
This interface is implemented by individual providers, but is
aggregated globally at the system bus name
<literal>org.freedesktop.realmd</literal>
with the object path <literal>/org/freedesktop/realmd</literal>
-->
<interface name="org.freedesktop.realmd.Provider">
<!--
Name: the name of the provider
The name of the provider. This is not normally displayed
to the user, but may be useful for diagnostics or debugging.
-->
<property name="Name" type="s" access="read"/>
<!--
Version: the version of the provider
The version of the provider. This is not normally used in
logic, but may be useful for diagnostics or debugging.
-->
<property name="Version" type="s" access="read"/>
<!--
Realms: a list of realms
A list of known, enrolled or discovered realms. All realms
that this provider knows about are listed here. As realms
are discovered they are added to this list.
Each realm is represented by the DBus object path of the
realm object.
-->
<property name="Realms" type="ao" access="read"/>
<!--
Discover:
@string: an input string to discover realms for
@options: options for the discovery operation
@relevance: the relevance of the returned results
@realm: a list of realms discovered
Discover realms for the given string. The input @string is
usually a domain or realm name, perhaps typed by a user. If
an empty string is provided the realm provider should try to
discover a default realm if possible (eg: from DHCP).
@options can contain, but is not limited to, the following values:
<itemizedlist>
<listitem><para><literal>operation</literal>: a string
identifier chosen by the client, which can then later be
passed to org.freedesktop.realmd.Service.Cancel() in order
to cancel the operation</para></listitem>
</itemizedlist>
The @relevance returned can be used to rank results from
different discover calls to different providers. Implementors
should return a positive number if the provider highly
recommends that the realms be handled by this provider,
or a zero if it can possibly handle the realms. Negative
should be returned if no realms are found.
This method does not return an error when no realms are
discovered. It simply returns an @realm list.
To see diagnostic information about the discovery process
connect to the org.freedesktop.realmd.Service::Diagnostics
signal.
This method requires authorization for the PolicyKit action
called <literal>org.freedesktop.realmd.discover-realm</literal>.
In addition to common DBus error results, this method may
return:
<itemizedlist>
<listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
may be returned if the discovery could not be run for some reason.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
returned if the operation was cancelled.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
returned if the calling client is not permitted to perform a discovery
operation.</para></listitem>
</itemizedlist>
-->
<method name="Discover">
<arg name="string" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
<arg name="relevance" type="i" direction="out"/>
<arg name="realm" type="ao" direction="out"/>
</method>
</interface>
<!--
org.freedesktop.realmd.Service:
@short_description: the realmd service
Global calls for managing the realmd service. Usually you'll want
to use #org.freedesktop.realmd.Provider instead.
This interface is implemented by the realmd service, and is always
available at the object path <literal>/org/freedesktop/realmd</literal>
The service also implements the
<literal>org.freedesktop.DBus.ObjectManager</literal> interface which
makes it easy to retrieve all realmd objects and properties in one go.
-->
<interface name="org.freedesktop.realmd.Service">
<!--
Cancel:
@operation: the operation to cancel
Cancel a realmd operation. To be able to cancel an operation
pass a uniquely chosen <literal>operation</literal> string
identifier as an option in the methods <literal>options</literal>
argument.
These operation string identifiers should be unique per client
calling the realmd service.
It is not guaranteed that the service can or will cancel the
operation. For example the operation may have already completed
by the time this method is handled. The caller of the operation
method will receive a
<literal>org.freedesktop.realmd.Error.Cancelled</literal>
if the operation was cancelled.
-->
<method name="Cancel">
<arg name="operation" type="s" direction="in"/>
</method>
<!--
SetLocale:
@locale: the locale for the client
Set the language @locale for the client. This locale is used
for error messages. The locale is used until the next time
this method is called, the client disconnects, or the client
calls #org.freedesktop.realmd.Service.Release().
-->
<method name="SetLocale">
<arg name="locale" type="s" direction="in"/>
</method>
<!--
Diagnostics:
@data: diagnostic data
@operation: the operation this data resulted from
This signal is fired when diagnostics result from an operation
in the provider or one of its realms.
It is not guaranteed that this signal is emitted once per line.
More than one line may be contained in @data, or a partial
line. New line characters are embedded in @data.
This signal is sent explicitly to the client which invoked
operation method. In order to tell which operation this
diagnostic data results from, pass a unique
<literal>operation</literal> string identifier in the
<literal>options</literal> argument of the operation method.
That same identifier will be passed back via the @operation
argument of this signal.
-->
<signal name="Diagnostics">
<arg name="data" type="s"/>
<arg name="operation" type="s"/>
</signal>
<!--
Release:
Normally realmd waits until all clients have disconnected
before exiting itself, sometime later. For long lived clients
they can call this method to allow the realmd service to quit.
This is an optimization. The daemon will not exit immediately.
It is safe to call this multiple times.
-->
<method name="Release">
<!-- no arguments -->
</method>
</interface>
<!--
org.freedesktop.realmd.Realm:
@short_description: a realm
Represents one realm.
Contains generic information about a realm, and useful properties for
introspecting what kind of realm this is and how to work with
the realm.
Use #org.freedesktop.realmd.Provider:Realms or
#org.freedesktop.realmd.Provider.Discover() to get access to some
kerberos realm objects.
Realms will always implement additional interfaces, such as
#org.freedesktop.realmd.Kerberos. Do not assume that all realms
implement that kerberos interface. Use the
#org.freedesktop.realmd.Realm:SupportedInterfaces property to see
which interfaces are set.
Different realms support various ways to configure them on the
system. Use the #org.freedesktop.realmd.Realm:Configured property
to determine if a realm is configured. If it is configured the
property will be set to the interface of the mechanism that was
used to configure it.
To configure a realm, look in the
#org.freedesktop.realmd.Realm:SupportedInterfaces property for a
recognized purpose specific interface that can be used for
configuration, such as the
#org.freedesktop.realmd.KerberosMembership interface and its
#org.freedesktop.realmd.KerberosMembership.Join() method.
To deconfigure a realm from the current system, you can use the
#org.freedesktop.realmd.Realm.Deconfigure() method. In additon some
of the configuration specific interfaces provide methods to
deconfigure a realm in a specific way, such as
#org.freedesktop.realmd.KerberosMembership.Leave() method.
The various properties are guaranteed to have been updated before
the operation methods return, if they change state.
-->
<interface name="org.freedesktop.realmd.Realm">
<!--
Name: the realm name
This is the name of the realm, appropriate for display to
end users where necessary.
-->
<property name="Name" type="s" access="read"/>
<!--
Configured: whether this domain is configured and how
If this property is an empty string, then the realm is not
configured. Otherwise the realm is configured, and contains
a string which is the interface that represents how it was
configured, for example #org.freedesktop.realmd.KerberosMembership.
-->
<property name="Configured" type="s" access="read"/>
<!--
Deconfigure: deconfigure this realm
Deconfigure this realm from the local machine with standard
default behavior.
The behavior of this method depends on the which configuration
interface is present in the
#org.freedesktop.realmd.Realm.Configured property. It does not
always delete membership accounts in the realm, but just
reconfigures the local machine so it no longer is configured
for the given realm. In some cases the implementation may try
to update membership accounts, but this is not guaranteed.
Various configuration interfaces may support more specific ways
to deconfigure a realm in a specific way, such as the
#org.freedesktop.realmd.KerberosMembership.Leave() method.
@options can contain, but is not limited to, the following values:
<itemizedlist>
<listitem><para><literal>operation</literal>: a string
identifier chosen by the client, which can then later be
passed to org.freedesktop.realmd.Service.Cancel() in order
to cancel the operation</para></listitem>
</itemizedlist>
This method requires authorization for the PolicyKit action
called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
In addition to common DBus error results, this method may return:
<itemizedlist>
<listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
may be returned if the deconfigure failed for a generic reason.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
returned if the operation was cancelled.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
returned if the calling client is not permitted to deconfigure a
realm.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
returned if this realm is not configured on the machine.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
returned if the service is currently performing another operation like
join or leave.</para></listitem>
</itemizedlist>
-->
<method name="Deconfigure">
<arg name="options" type="a{sv}" direction="in"/>
</method>
<!--
SupportedInterfaces:
Additional supported interfaces of this realm. This includes
interfaces that contain more information about the realm,
such as #org.freedesktop.realmd.Kerberos and interfaces
which contain methods for configuring a realm, such as
#org.freedesktop.realmd.KerberosMembership.
-->
<property name="SupportedInterfaces" type="as" access="read"/>
<!--
Details: informational details about the realm
Informational details about the realm. The following values
should be present:
<itemizedlist>
<listitem><para><literal>server-software</literal>:
identifier of the software running on the server (eg:
<literal>active-directory</literal>).</para></listitem>
<listitem><para><literal>client-software</literal>:
identifier of the software running on the client (eg:
<literal>sssd</literal>).</para></listitem>
</itemizedlist>
-->
<property name="Details" type="a(ss)" access="read"/>
<!--
LoginFormats: supported formats for login names
Supported formats for login to this realm. This is only
relevant once the realm has been enrolled. The formats
will contain a <literal>%U</literal> in the string, which
indicate where the user name should be placed. The formats
may contain a <literal>%D</literal> in the string which
indicate where a domain name should be placed.
The first format in the list is the preferred format for
login names.
-->
<property name="LoginFormats" type="as" access="read"/>
<!--
LoginPolicy: the policy for logins using this realm
The policy for logging into this computer using this realm.
The policy can be changed using the
#org.freedesktop.realmd.Realm.ChangeLoginPolicy() method.
The following policies are predefined. Not all providers
support all these policies and there may be provider specific
policies or multiple policies represented in the string:
<itemizedlist>
<listitem><para><literal>allow-any-login</literal>: allow
login by any authenticated user present in this
realm.</para></listitem>
<listitem><para><literal>allow-permitted-logins</literal>:
only allow the logins permitted in the
#org.freedesktop.realmd.Realm:PermittedLogins
property.</para></listitem>
<listitem><para><literal>deny-any-login</literal>:
don't allow any logins via authenticated users of this
realm.</para></listitem>
</itemizedlist>
-->
<property name="LoginPolicy" type="s" access="read"/>
<!--
PermittedLogins: the permitted login names
The list of permitted authenticated users allowed to login
into this computer. This is only relevant if the
#org.freedesktop.realmd.Realm:LoginPolicy property
contains the <literal>allow-permitted-logins</literal>
string.
-->
<property name="PermittedLogins" type="as" access="read"/>
<!--
ChangeLoginPolicy:
@login_policy: the new login policy, or an empty string
@permitted_add: a list of logins to permit
@permitted_remove: a list of logins to not permit
@options: options for this operation
Change the login policy and/or permitted logins for this realm.
Not all realms support the all the various login policies. An
error will be returned if the new login policy is not supported.
You may specify an empty string for the @login_policy argument
which will cause no change in the policy itself. If the policy
is changed, it will be reflected in the
#org.freedesktop.realmd.Realm:LoginPolicy property.
The @permitted_add and @permitted_remove arguments represent
lists of login names that should be added and removed from
the #org.freedesktop.realmd.Kerberos:PermittedLogins property.
@options can contain, but is not limited to, the following values:
<itemizedlist>
<listitem><para><literal>operation</literal>: a string
identifier chosen by the client, which can then later be
passed to org.freedesktop.realmd.Service.Cancel() in order
to cancel the operation</para></listitem>
</itemizedlist>
This method requires authorization for the PolicyKit action
called <literal>org.freedesktop.realmd.login-policy</literal>.
In addition to common DBus error results, this method may return:
<itemizedlist>
<listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
may be returned if the policy change failed for a generic reason.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
returned if the operation was cancelled.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
returned if the calling client is not permitted to change login policy
operation.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotConfigured</literal>:
returned if the realm is not configured.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
returned if the service is currently performing another operation like
join or leave.</para></listitem>
</itemizedlist>
-->
<method name="ChangeLoginPolicy">
<arg name="login_policy" type="s" direction="in"/>
<arg name="permitted_add" type="as" direction="in"/>
<arg name="permitted_remove" type="as" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method>
</interface>
<!--
org.freedesktop.realmd.Kerberos:
@short_description: a kerberos realm
An interface that describes a kerberos realm in more detail. This
is always implemented on an DBus object path that also implements
the #org.freedesktop.realmd.Realm interface.
-->
<interface name="org.freedesktop.realmd.Kerberos">
<!--
RealmName: the kerberos realm name
The kerberos name for this realm. This is usually in upper
case.
-->
<property name="RealmName" type="s" access="read"/>
<!--
DomainName: the DNS domain name
The DNS domain name for this realm.
-->
<property name="DomainName" type="s" access="read"/>
</interface>
<!--
org.freedesktop.realmd.KerberosMembership:
An interface used to configure this machine by joining a realm.
It sets up a computer/host account in the realm for this machine
and a keytab to track the credentials for that account.
The various properties are guaranteed to have been updated before
the operation methods return, if they change state.
-->
<interface name="org.freedesktop.realmd.KerberosMembership">
<!--
SuggestedAdministrator: common administrator name
The common administrator name for this type of realm. This
can be used by clients as a hint when prompting the user for
administrative authentication.
-->
<property name="SuggestedAdministrator" type="s" access="read"/>
<!--
SupportedJoinCredentials: credentials supported for joining
Various kinds of credentials that are supported when calling the
#org.freedesktop.realmd.Kerberos.Join() method.
Each credential is represented by a type, and an owner. The type
denotes which kind of credential is passed to the method. The
owner indicates to the client how to prompt the user or obtain
the credential, and to the service how to use the credential.
The various types are:
<itemizedlist>
<listitem><para><literal>ccache</literal>:
the credentials should contain an array of bytes as a
<literal>ay</literal> containing the data from a kerberos
credential cache file.</para></listitem>
<listitem><para><literal>password</literal>:
the credentials should contain a pair of strings as a
<literal>(ss)</literal> representing a name and
password. The name may contain a realm in the standard
kerberos format. If missing, it will default to this
realm. The name may be empty for a computer or one time
password.</para></listitem>
<listitem><para><literal>automatic</literal>:
the credentials should contain an empty string as a
<literal>s</literal>. Using <literal>automatic</literal>
indicates that default or system credentials are to be
used.</para></listitem>
</itemizedlist>
The various owners are:
<itemizedlist>
<listitem><para><literal>administrator</literal>:
the credentials belong to a kerberos user principal.
The caller may use this as a hint to prompt the user
for administrative credentials.</para></listitem>
<listitem><para><literal>user</literal>:
the credentials belong to a kerberos user principal.
The caller may use this as a hint to prompt the user
for his (possibly non-administrative)
credentials.</para></listitem>
<listitem><para><literal>computer</literal>:
the credentials belong to the computer realmd is
being run on.</para></listitem>
<listitem><para><literal>secret</literal>:
the credentials are a one time password or other secret
used to join or leave the computer.</para></listitem>
</itemizedlist>
-->
<property name="SupportedJoinCredentials" type="a(ss)" access="read"/>
<!--
SupportedLeaveCredentials: credentials supported for leaving
Various kinds of credentials that are supported when calling the
#org.freedesktop.realmd.Kerberos.Leave() method.
See #org.freedesktop.realmd.Kerberos:SupportedJoinCredentials for
a discussion of what the values represent.
-->
<property name="SupportedLeaveCredentials" type="a(ss)" access="read"/>
<!--
Join:
Join this machine to the realm and enroll the machine.
If this method returns successfully then the machine will be
joined to the realm. It is not necessary to restart services or the
machine afterward. Relevant properties on the realm will be updated
before the method returns.
The @credentials should be set according to one of the
supported credentials returned by
#org.freedesktop.realmd.Kerberos:SupportedJoinCredentials.
The first string in the tuple is the type, the second string
is the owner, and the variant contains the credential contents
See the discussion at
#org.freedesktop.realmd.Kerberos:SupportedJoinCredentials
for more information.
@options can contain, but is not limited to, the following values:
<itemizedlist>
<listitem><para><literal>operation</literal>: a string
identifier chosen by the client, which can then later be
passed to org.freedesktop.realmd.Service.Cancel() in order
to cancel the operation</para></listitem>
</itemizedlist>
This method requires authorization for the PolicyKit action
called <literal>org.freedesktop.realmd.configure-realm</literal>.
In addition to common DBus error results, this method may return:
<itemizedlist>
<listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
may be returned if the join failed for a generic reason.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
returned if the operation was cancelled.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
returned if the calling client is not permitted to perform an join
operation.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
returned if the credentials passed did not authenticate against the realm
correctly. It is appropriate to prompt the user again.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.AlreadyEnrolled</literal>:
returned if already enrolled in this realm, or another realm and enrolling
in multiple realms is not supported.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
returned if the service is currently performing another operation like
join or leave.</para></listitem>
</itemizedlist>
-->
<method name="Join">
<arg name="credentials" type="(ssv)" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method>
<!--
Leave:
Leave the realm and unenroll the machine.
If this method returns successfully then the machine will have
left the domain and been unenrolled. It is not necessary to restart
services or the machine afterward. Relevant properties on the realm
will be updated before the method returns.
The @credentials should be set according to one of the
supported credentials returned by
#org.freedesktop.realmd.Kerberos:SupportedUnenrollCredentials.
The first string in the tuple is the type, the second string
is the owner, and the variant contains the credential contents
See the discussion at
#org.freedesktop.realmd.Kerberos:SupportedEnrollCredentials
for more information.
@options can contain, but is not limited to, the following values:
<itemizedlist>
<listitem><para><literal>operation</literal>: a string
identifier chosen by the client, which can then later be
passed to org.freedesktop.realmd.Service.Cancel() in order
to cancel the operation</para></listitem>
</itemizedlist>
This method requires authorization for the PolicyKit action
called <literal>org.freedesktop.realmd.deconfigure-realm</literal>.
In addition to common DBus error results, this method may return:
<itemizedlist>
<listitem><para><literal>org.freedesktop.realmd.Error.Failed</literal>:
may be returned if the unenroll failed for a generic reason.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Cancelled</literal>:
returned if the operation was cancelled.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotAuthorized</literal>:
returned if the calling client is not permitted to perform an unenroll
operation.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.AuthenicationFailed</literal>:
returned if the credentials passed did not authenticate against the realm
correctly. It is appropriate to prompt the user again.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.NotEnrolled</literal>:
returned if not enrolled in this realm.</para></listitem>
<listitem><para><literal>org.freedesktop.realmd.Error.Busy</literal>:
returned if the service is currently performing another operation like
enroll or unenroll.</para></listitem>
</itemizedlist>
-->
<method name="Leave">
<arg name="credentials" type="(ssv)" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
</method>
</interface>
</node>

View file

@ -0,0 +1,252 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <adwaita.h>
#include "um-photo-dialog.h"
#include "um-utils.h"
#define ROW_SPAN 5
#define AVATAR_PIXEL_SIZE 72
struct _UmPhotoDialog {
GtkPopover parent;
GtkWidget *flowbox;
GListStore *faces;
SelectAvatarCallback *callback;
gpointer data;
};
G_DEFINE_TYPE (UmPhotoDialog, um_photo_dialog, GTK_TYPE_POPOVER)
static void
webcam_icon_selected (UmPhotoDialog *um)
{
g_warning ("Webcam icon selected, but compiled without Cheese support");
}
static void
face_widget_activated (GtkFlowBox *flowbox,
GtkFlowBoxChild *child,
UmPhotoDialog *um)
{
const char *filename;
GtkWidget *image;
image = gtk_flow_box_child_get_child (child);
filename = g_object_get_data (G_OBJECT (image), "filename");
um->callback (filename, um->data);
gtk_popover_popdown (GTK_POPOVER (um));
}
static GtkWidget *
create_face_widget (gpointer item,
gpointer user_data)
{
g_autoptr(GdkTexture) texture = NULL;
g_autofree gchar *path = g_file_get_path (G_FILE (item));
GtkWidget *image;
image = adw_avatar_new (AVATAR_PIXEL_SIZE, NULL, TRUE);
texture = gdk_texture_new_from_file (G_FILE (item), NULL);
adw_avatar_set_custom_image (ADW_AVATAR (image), GDK_PAINTABLE (texture));
g_object_set_data_full (G_OBJECT (image),
"filename", g_steal_pointer (&path),
(GDestroyNotify) g_free);
return image;
}
static GStrv
get_settings_facesdirs (void)
{
g_autoptr(GSettingsSchema) schema = NULL;
g_autoptr(GPtrArray) facesdirs = g_ptr_array_new ();
g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
if (settings_dirs != NULL) {
int i;
for (i = 0; settings_dirs[i] != NULL; i++) {
char *path = settings_dirs[i];
if (path != NULL && g_strcmp0 (path, "") != 0)
g_ptr_array_add (facesdirs, g_strdup (path));
}
}
// NULL terminated array
g_ptr_array_add (facesdirs, NULL);
return (GStrv) g_steal_pointer (&facesdirs->pdata);
}
static GStrv
get_system_facesdirs (void)
{
g_autoptr(GPtrArray) facesdirs = NULL;
const char * const * data_dirs;
int i;
facesdirs = g_ptr_array_new ();
data_dirs = g_get_system_data_dirs ();
for (i = 0; data_dirs[i] != NULL; i++) {
char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
g_ptr_array_add (facesdirs, path);
}
// NULL terminated array
g_ptr_array_add (facesdirs, NULL);
return (GStrv) g_steal_pointer (&facesdirs->pdata);
}
static gboolean
add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
{
gboolean added_faces = FALSE;
const gchar *target;
int i;
GFileType type;
for (i = 0; facesdirs[i] != NULL; i++) {
g_autoptr(GFileEnumerator) enumerator = NULL;
g_autoptr(GFile) dir = NULL;
const char *path = facesdirs[i];
gpointer infoptr;
dir = g_file_new_for_path (path);
enumerator = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
G_FILE_QUERY_INFO_NONE,
NULL, NULL);
if (enumerator == NULL)
continue;
while ((infoptr = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
g_autoptr (GFileInfo) info = infoptr;
g_autoptr (GFile) face_file = NULL;
type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_SYMBOLIC_LINK)
continue;
target = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
if (target != NULL && g_str_has_prefix (target , "legacy/"))
continue;
face_file = g_file_get_child (dir, g_file_info_get_name (info));
g_list_store_append (faces, face_file);
added_faces = TRUE;
}
g_file_enumerator_close (enumerator, NULL, NULL);
if (added_faces && !add_all)
break;
}
return added_faces;
}
static void
setup_photo_popup (UmPhotoDialog *um)
{
g_auto(GStrv) facesdirs;
gboolean added_faces = FALSE;
um->faces = g_list_store_new (G_TYPE_FILE);
gtk_flow_box_bind_model (GTK_FLOW_BOX (um->flowbox),
G_LIST_MODEL (um->faces),
create_face_widget,
um,
NULL);
g_signal_connect (um->flowbox, "child-activated",
G_CALLBACK (face_widget_activated), um);
facesdirs = get_settings_facesdirs ();
added_faces = add_faces_from_dirs (um->faces, facesdirs, TRUE);
if (!added_faces) {
facesdirs = get_system_facesdirs ();
add_faces_from_dirs (um->faces, facesdirs, FALSE);
}
}
UmPhotoDialog *
um_photo_dialog_new (SelectAvatarCallback callback,
gpointer data)
{
UmPhotoDialog *um;
um = g_object_new (UM_TYPE_PHOTO_DIALOG, NULL);
setup_photo_popup (um);
um->callback = callback;
um->data = data;
return um;
}
void
um_photo_dialog_dispose (GObject *object)
{
G_OBJECT_CLASS (um_photo_dialog_parent_class)->dispose (object);
}
static void
um_photo_dialog_init (UmPhotoDialog *um)
{
gtk_widget_init_template (GTK_WIDGET (um));
}
static void
um_photo_dialog_class_init (UmPhotoDialogClass *klass)
{
GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
GObjectClass *oclass = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/initial-setup/gis-account-avatar-chooser.ui");
gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, flowbox);
gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected);
oclass->dispose = um_photo_dialog_dispose;
}

View file

@ -0,0 +1,46 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __UM_PHOTO_DIALOG_H__
#define __UM_PHOTO_DIALOG_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define UM_TYPE_PHOTO_DIALOG (um_photo_dialog_get_type())
G_DECLARE_FINAL_TYPE (UmPhotoDialog, um_photo_dialog, UM, PHOTO_DIALOG, GtkPopover)
typedef struct _UmPhotoDialog UmPhotoDialog;
typedef void (SelectAvatarCallback) (const gchar *filename,
gpointer data);
UmPhotoDialog *um_photo_dialog_new (SelectAvatarCallback callback,
gpointer data);
void um_photo_dialog_free (UmPhotoDialog *dialog);
void um_photo_dialog_set_generated_avatar_text (UmPhotoDialog *dialog,
const gchar *name);
G_END_DECLS
#endif

View file

@ -0,0 +1,878 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2012 Red Hat, Inc.
*
* 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 2 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/>.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
* Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include "um-realm-manager.h"
#include <krb5/krb5.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
struct _UmRealmManager {
UmRealmObjectManagerClient parent;
UmRealmProvider *provider;
};
typedef struct {
UmRealmProviderProxyClass parent_class;
} UmRealmManagerClass;
enum {
REALM_ADDED,
NUM_SIGNALS,
};
static gint signals[NUM_SIGNALS] = { 0, };
G_DEFINE_TYPE (UmRealmManager, um_realm_manager, UM_REALM_TYPE_OBJECT_MANAGER_CLIENT);
GQuark
um_realm_error_get_quark (void)
{
static GQuark quark = 0;
if (quark == 0)
quark = g_quark_from_static_string ("um-realm-error");
return quark;
}
static gboolean
is_realm_with_kerberos_and_membership (gpointer object)
{
GDBusInterface *interface;
if (!G_IS_DBUS_OBJECT (object))
return FALSE;
interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.Kerberos");
if (interface == NULL)
return FALSE;
g_object_unref (interface);
interface = g_dbus_object_get_interface (object, "org.freedesktop.realmd.KerberosMembership");
if (interface == NULL)
return FALSE;
g_object_unref (interface);
return TRUE;
}
static void
on_interface_added (GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface)
{
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (interface), G_MAXINT);
}
static void
on_object_added (GDBusObjectManager *manager,
GDBusObject *object,
gpointer user_data)
{
GList *interfaces, *l;
interfaces = g_dbus_object_get_interfaces (object);
for (l = interfaces; l != NULL; l = g_list_next (l))
on_interface_added (manager, object, l->data);
g_list_free_full (interfaces, g_object_unref);
if (is_realm_with_kerberos_and_membership (object)) {
g_debug ("Saw realm: %s", g_dbus_object_get_object_path (object));
g_signal_emit (user_data, signals[REALM_ADDED], 0, object);
}
}
static void
um_realm_manager_init (UmRealmManager *self)
{
g_signal_connect (self, "object-added", G_CALLBACK (on_object_added), self);
g_signal_connect (self, "interface-added", G_CALLBACK (on_interface_added), self);
}
static void
um_realm_manager_dispose (GObject *obj)
{
UmRealmManager *self = UM_REALM_MANAGER (obj);
g_clear_object (&self->provider);
G_OBJECT_CLASS (um_realm_manager_parent_class)->dispose (obj);
}
static void
um_realm_manager_class_init (UmRealmManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = um_realm_manager_dispose;
signals[REALM_ADDED] = g_signal_new ("realm-added", UM_TYPE_REALM_MANAGER,
G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 1, UM_REALM_TYPE_OBJECT);
}
typedef struct {
GCancellable *cancellable;
UmRealmManager *manager;
} NewClosure;
static void
new_closure_free (gpointer data)
{
NewClosure *closure = data;
g_clear_object (&closure->cancellable);
g_clear_object (&closure->manager);
g_slice_free (NewClosure, closure);
}
static void
on_provider_new (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async);
GError *error = NULL;
UmRealmProvider *provider;
provider = um_realm_provider_proxy_new_finish (result, &error);
closure->manager->provider = provider;
if (error == NULL) {
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (closure->manager->provider), -1);
g_debug ("Created realm manager");
} else {
g_simple_async_result_take_error (async, error);
}
g_simple_async_result_complete (async);
g_object_unref (async);
}
static void
on_manager_new (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
NewClosure *closure = g_simple_async_result_get_op_res_gpointer (async);
GDBusConnection *connection;
GError *error = NULL;
GObject *object;
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, &error);
if (error == NULL) {
closure->manager = UM_REALM_MANAGER (object);
connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object));
g_debug ("Connected to realmd");
um_realm_provider_proxy_new (connection,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
"org.freedesktop.realmd",
"/org/freedesktop/realmd",
closure->cancellable,
on_provider_new, g_object_ref (async));
} else {
g_simple_async_result_take_error (async, error);
g_simple_async_result_complete (async);
}
g_object_unref (async);
}
void
um_realm_manager_new (GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *async;
NewClosure *closure;
g_debug ("Connecting to realmd...");
async = g_simple_async_result_new (NULL, callback, user_data,
um_realm_manager_new);
closure = g_slice_new (NewClosure);
closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
g_simple_async_result_set_op_res_gpointer (async, closure, new_closure_free);
g_async_initable_new_async (UM_TYPE_REALM_MANAGER, G_PRIORITY_DEFAULT,
cancellable, on_manager_new, g_object_ref (async),
"flags", G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
"name", "org.freedesktop.realmd",
"bus-type", G_BUS_TYPE_SYSTEM,
"object-path", "/org/freedesktop/realmd",
"get-proxy-type-func", um_realm_object_manager_client_get_proxy_type,
NULL);
g_object_unref (async);
}
UmRealmManager *
um_realm_manager_new_finish (GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *async;
NewClosure *closure;
g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
um_realm_manager_new), NULL);
async = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (async, error))
return NULL;
closure = g_simple_async_result_get_op_res_gpointer (async);
return g_object_ref (closure->manager);
}
typedef struct {
UmRealmManager *manager;
GCancellable *cancellable;
GList *realms;
} DiscoverClosure;
static void
discover_closure_free (gpointer data)
{
DiscoverClosure *discover = data;
g_object_unref (discover->manager);
g_clear_object (&discover->cancellable);
g_list_free_full (discover->realms, g_object_unref);
g_slice_free (DiscoverClosure, discover);
}
static void
on_provider_discover (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async);
GDBusObject *object;
GError *error = NULL;
gboolean no_membership = FALSE;
gchar **realms;
gint relevance;
gint i;
um_realm_provider_call_discover_finish (UM_REALM_PROVIDER (source), &relevance,
&realms, result, &error);
if (error == NULL) {
for (i = 0; realms[i]; i++) {
object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (discover->manager), realms[i]);
if (object == NULL) {
g_warning ("Realm is not in object manager: %s", realms[i]);
} else {
if (is_realm_with_kerberos_and_membership (object)) {
g_debug ("Discovered realm: %s", realms[i]);
discover->realms = g_list_prepend (discover->realms, object);
} else {
g_debug ("Realm does not support kerberos membership: %s", realms[i]);
no_membership = TRUE;
g_object_unref (object);
}
}
}
g_strfreev (realms);
if (!discover->realms && no_membership) {
g_simple_async_result_set_error (async, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
_("Cannot automatically join this type of domain"));
}
} else {
g_simple_async_result_take_error (async, error);
}
g_simple_async_result_complete (async);
g_object_unref (async);
}
void
um_realm_manager_discover (UmRealmManager *self,
const gchar *input,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
DiscoverClosure *discover;
GVariant *options;
g_return_if_fail (UM_IS_REALM_MANAGER (self));
g_return_if_fail (input != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_debug ("Discovering realms for: %s", input);
res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
um_realm_manager_discover);
discover = g_slice_new0 (DiscoverClosure);
discover->manager = g_object_ref (self);
discover->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free);
options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
um_realm_provider_call_discover (self->provider, input, options, cancellable,
on_provider_discover, g_object_ref (res));
g_object_unref (res);
}
GList *
um_realm_manager_discover_finish (UmRealmManager *self,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *async;
DiscoverClosure *discover;
GList *realms;
g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
um_realm_manager_discover), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
async = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (async, error))
return NULL;
discover = g_simple_async_result_get_op_res_gpointer (async);
if (!discover->realms) {
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
_("No such domain or realm found"));
return NULL;
}
realms = g_list_reverse (discover->realms);
discover->realms = NULL;
return realms;
}
GList *
um_realm_manager_get_realms (UmRealmManager *self)
{
GList *objects;
GList *realms = NULL;
GList *l;
g_return_val_if_fail (UM_IS_REALM_MANAGER (self), NULL);
objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self));
for (l = objects; l != NULL; l = g_list_next (l)) {
if (is_realm_with_kerberos_and_membership (l->data))
realms = g_list_prepend (realms, g_object_ref (l->data));
}
g_list_free_full (objects, g_object_unref);
return realms;
}
static void
string_replace (GString *string,
const gchar *find,
const gchar *replace)
{
const gchar *at;
gssize pos;
at = strstr (string->str, find);
if (at != NULL) {
pos = at - string->str;
g_string_erase (string, pos, strlen (find));
g_string_insert (string, pos, replace);
}
}
gchar *
um_realm_calculate_login (UmRealmCommon *realm,
const gchar *username)
{
GString *string;
const gchar *const *formats;
gchar *login = NULL;
formats = um_realm_common_get_login_formats (realm);
if (formats[0] != NULL) {
string = g_string_new (formats[0]);
string_replace (string, "%U", username);
string_replace (string, "%D", um_realm_common_get_name (realm));
login = g_string_free (string, FALSE);
}
return login;
}
gboolean
um_realm_is_configured (UmRealmObject *realm)
{
UmRealmCommon *common;
const gchar *configured;
gboolean is = FALSE;
common = um_realm_object_get_common (realm);
if (common) {
configured = um_realm_common_get_configured (common);
is = configured != NULL && !g_str_equal (configured, "");
g_object_unref (common);
}
return is;
}
static const gchar *
find_supported_credentials (UmRealmKerberosMembership *membership,
const gchar *owner)
{
const gchar *cred_owner;
const gchar *cred_type;
GVariant *supported;
GVariantIter iter;
supported = um_realm_kerberos_membership_get_supported_join_credentials (membership);
g_return_val_if_fail (supported != NULL, NULL);
g_variant_iter_init (&iter, supported);
while (g_variant_iter_loop (&iter, "(&s&s)", &cred_type, &cred_owner)) {
if (g_str_equal (owner, cred_owner)) {
if (g_str_equal (cred_type, "ccache") ||
g_str_equal (cred_type, "password")) {
return g_intern_string (cred_type);
}
}
}
return NULL;
}
static void
on_realm_join_complete (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
g_debug ("Completed Join() method call");
g_simple_async_result_set_op_res_gpointer (async, g_object_ref (result), g_object_unref);
g_simple_async_result_complete_in_idle (async);
g_object_unref (async);
}
static gboolean
realm_join_as_owner (UmRealmObject *realm,
const gchar *owner,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
UmRealmKerberosMembership *membership;
GSimpleAsyncResult *async;
GVariant *contents;
GVariant *options;
GVariant *option;
GVariant *creds;
const gchar *type;
membership = um_realm_object_get_kerberos_membership (realm);
g_return_val_if_fail (membership != NULL, FALSE);
type = find_supported_credentials (membership, owner);
if (type == NULL) {
g_debug ("Couldn't find supported credential type for owner: %s", owner);
g_object_unref (membership);
return FALSE;
}
async = g_simple_async_result_new (G_OBJECT (realm), callback, user_data,
realm_join_as_owner);
if (g_str_equal (type, "ccache")) {
g_debug ("Using a kerberos credential cache to join the realm");
contents = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
g_bytes_get_data (credentials, NULL),
g_bytes_get_size (credentials),
TRUE, (GDestroyNotify)g_bytes_unref, credentials);
} else if (g_str_equal (type, "password")) {
g_debug ("Using a user/password to join the realm");
contents = g_variant_new ("(ss)", login, password);
} else {
g_assert_not_reached ();
}
creds = g_variant_new ("(ssv)", type, owner, contents);
option = g_variant_new ("{sv}", "manage-system", g_variant_new_boolean (FALSE));
options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), &option, 1);
g_debug ("Calling the Join() method with %s credentials", owner);
um_realm_kerberos_membership_call_join (membership, creds, options,
cancellable, on_realm_join_complete,
g_object_ref (async));
g_object_unref (async);
g_object_unref (membership);
return TRUE;
}
gboolean
um_realm_join_as_user (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (login != NULL, FALSE);
g_return_val_if_fail (password != NULL, FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
return realm_join_as_owner (realm, "user", login, password,
credentials, cancellable, callback, user_data);
}
gboolean
um_realm_join_as_admin (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (login != NULL, FALSE);
g_return_val_if_fail (password != NULL, FALSE);
g_return_val_if_fail (credentials != NULL, FALSE);
return realm_join_as_owner (realm, "administrator", login, password, credentials,
cancellable, callback, user_data);
}
gboolean
um_realm_join_finish (UmRealmObject *realm,
GAsyncResult *result,
GError **error)
{
UmRealmKerberosMembership *membership;
GError *call_error = NULL;
gchar *dbus_error;
GAsyncResult *async;
g_return_val_if_fail (UM_REALM_IS_OBJECT (realm), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
membership = um_realm_object_get_kerberos_membership (realm);
g_return_val_if_fail (membership != NULL, FALSE);
async = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
um_realm_kerberos_membership_call_join_finish (membership, async, &call_error);
g_object_unref (membership);
if (call_error == NULL)
return TRUE;
dbus_error = g_dbus_error_get_remote_error (call_error);
if (dbus_error == NULL) {
g_debug ("Join() failed because of %s", call_error->message);
g_propagate_error (error, call_error);
return FALSE;
}
g_dbus_error_strip_remote_error (call_error);
if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.AuthenticationFailed")) {
g_debug ("Join() failed because of invalid/insufficient credentials");
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
"%s", call_error->message);
g_error_free (call_error);
} else if (g_str_equal (dbus_error, "org.freedesktop.realmd.Error.BadHostname")) {
g_debug ("Join() failed because of invalid/conflicting host name");
g_set_error (error, UM_REALM_ERROR, UM_REALM_ERROR_BAD_HOSTNAME,
"%s", call_error->message);
g_error_free (call_error);
} else {
g_debug ("Join() failed because of %s", call_error->message);
g_propagate_error (error, call_error);
}
g_free (dbus_error);
return FALSE;
}
typedef struct {
gchar *domain;
gchar *realm;
gchar *user;
gchar *password;
GBytes *credentials;
} LoginClosure;
static void
login_closure_free (gpointer data)
{
LoginClosure *login = data;
g_free (login->domain);
g_free (login->realm);
g_free (login->user);
g_free (login->password);
g_bytes_unref (login->credentials);
g_slice_free (LoginClosure, login);
}
static krb5_error_code
login_perform_kinit (krb5_context k5,
const gchar *realm,
const gchar *login,
const gchar *password,
const gchar *filename)
{
krb5_get_init_creds_opt *opts;
krb5_error_code code;
krb5_principal principal;
krb5_ccache ccache;
krb5_creds creds;
gchar *name;
name = g_strdup_printf ("%s@%s", login, realm);
code = krb5_parse_name (k5, name, &principal);
if (code != 0) {
g_debug ("Couldn't parse principal name: %s: %s",
name, krb5_get_error_message (k5, code));
g_free (name);
return code;
}
g_debug ("Using principal name to kinit: %s", name);
g_free (name);
if (filename == NULL)
code = krb5_cc_default (k5, &ccache);
else
code = krb5_cc_resolve (k5, filename, &ccache);
if (code != 0) {
krb5_free_principal (k5, principal);
g_debug ("Couldn't open credential cache: %s: %s",
filename ? filename : "<default>",
krb5_get_error_message (k5, code));
return code;
}
code = krb5_get_init_creds_opt_alloc (k5, &opts);
g_return_val_if_fail (code == 0, code);
code = krb5_get_init_creds_opt_set_out_ccache (k5, opts, ccache);
g_return_val_if_fail (code == 0, code);
code = krb5_get_init_creds_password (k5, &creds, principal,
(char *)password,
NULL, 0, 0, NULL, opts);
krb5_get_init_creds_opt_free (k5, opts);
krb5_cc_close (k5, ccache);
krb5_free_principal (k5, principal);
if (code == 0) {
g_debug ("kinit succeeded");
krb5_free_cred_contents (k5, &creds);
} else {
g_debug ("kinit failed: %s", krb5_get_error_message (k5, code));
}
return code;
}
static void
kinit_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
LoginClosure *login = task_data;
krb5_context k5 = NULL;
krb5_error_code code;
GError *error = NULL;
gchar *filename = NULL;
gchar *contents;
gsize length;
gint temp_fd;
filename = g_build_filename (g_get_user_runtime_dir (),
"um-krb5-creds.XXXXXX", NULL);
temp_fd = g_mkstemp_full (filename, O_RDWR, S_IRUSR | S_IWUSR);
if (temp_fd == -1) {
g_warning ("Couldn't create credential cache file: %s: %s",
filename, g_strerror (errno));
g_free (filename);
filename = NULL;
} else {
close (temp_fd);
}
code = krb5_init_context (&k5);
if (code == 0) {
code = login_perform_kinit (k5, login->realm, login->user,
login->password, filename);
}
switch (code) {
case 0:
if (filename != NULL) {
g_file_get_contents (filename, &contents, &length, &error);
if (error == NULL) {
login->credentials = g_bytes_new_take (contents, length);
g_debug ("Read in credential cache: %s", filename);
} else {
g_warning ("Couldn't read credential cache: %s: %s",
filename, error->message);
g_error_free (error);
}
}
g_task_return_boolean (task, TRUE);
break;
case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
case KRB5KDC_ERR_POLICY:
g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_LOGIN,
_("Cannot log in as %s at the %s domain"),
login->user, login->domain);
break;
case KRB5KDC_ERR_PREAUTH_FAILED:
case KRB5KRB_AP_ERR_BAD_INTEGRITY:
g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_BAD_PASSWORD,
_("Invalid password, please try again"));
break;
case KRB5_PREAUTH_FAILED:
case KRB5KDC_ERR_KEY_EXP:
case KRB5KDC_ERR_CLIENT_REVOKED:
case KRB5KDC_ERR_ETYPE_NOSUPP:
case KRB5_PROG_ETYPE_NOSUPP:
g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_CANNOT_AUTH,
_("Cannot log in as %s at the %s domain"),
login->user, login->domain);
break;
default:
g_task_return_new_error (task, UM_REALM_ERROR, UM_REALM_ERROR_GENERIC,
_("Couldnt connect to the %s domain: %s"),
login->domain, krb5_get_error_message (k5, code));
break;
}
if (filename) {
g_unlink (filename);
g_debug ("Deleted credential cache: %s", filename);
g_free (filename);
}
if (k5)
krb5_free_context (k5);
}
void
um_realm_login (UmRealmObject *realm,
const gchar *user,
const gchar *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
LoginClosure *login;
UmRealmKerberos *kerberos;
g_return_if_fail (UM_REALM_IS_OBJECT (realm));
g_return_if_fail (user != NULL);
g_return_if_fail (password != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
kerberos = um_realm_object_get_kerberos (realm);
g_return_if_fail (kerberos != NULL);
task = g_task_new (realm, cancellable, callback, user_data);
login = g_slice_new0 (LoginClosure);
login->domain = g_strdup (um_realm_kerberos_get_domain_name (kerberos));
login->realm = g_strdup (um_realm_kerberos_get_realm_name (kerberos));
login->user = g_strdup (user);
login->password = g_strdup (password);
g_task_set_task_data (task, login, login_closure_free);
g_task_set_check_cancellable (task, TRUE);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread (task, kinit_thread_func);
g_object_unref (task);
g_object_unref (kerberos);
}
gboolean
um_realm_login_finish (UmRealmObject *realm,
GAsyncResult *result,
GBytes **credentials,
GError **error)
{
GTask *task;
LoginClosure *login;
g_return_val_if_fail (g_task_is_valid (result, realm), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
task = G_TASK (result);
if (!g_task_propagate_boolean (task, error))
return FALSE;
login = g_task_get_task_data (task);
if (credentials) {
if (login->credentials)
*credentials = g_bytes_ref (login->credentials);
else
*credentials = NULL;
}
return TRUE;
}

View file

@ -0,0 +1,108 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2012 Red Hat, Inc.
*
* 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 2 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/>.
*
* Written by: Stef Walter <stefw@gnome.org>
*/
#ifndef __UM_REALM_MANAGER_H__
#define __UM_REALM_MANAGER_H__
#include "um-realm-generated.h"
G_BEGIN_DECLS
typedef enum {
UM_REALM_ERROR_BAD_LOGIN,
UM_REALM_ERROR_BAD_PASSWORD,
UM_REALM_ERROR_CANNOT_AUTH,
UM_REALM_ERROR_BAD_HOSTNAME,
UM_REALM_ERROR_GENERIC,
} UmRealmErrors;
#define UM_REALM_ERROR (um_realm_error_get_quark ())
GQuark um_realm_error_get_quark (void) G_GNUC_CONST;
#define UM_TYPE_REALM_MANAGER (um_realm_manager_get_type ())
#define UM_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_REALM_MANAGER, UmRealmManager))
#define UM_IS_REALM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_REALM_MANAGER))
typedef struct _UmRealmManager UmRealmManager;
GType um_realm_manager_get_type (void) G_GNUC_CONST;
void um_realm_manager_new (GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
UmRealmManager * um_realm_manager_new_finish (GAsyncResult *result,
GError **error);
void um_realm_manager_discover (UmRealmManager *self,
const gchar *input,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * um_realm_manager_discover_finish (UmRealmManager *self,
GAsyncResult *result,
GError **error);
GList * um_realm_manager_get_realms (UmRealmManager *self);
void um_realm_login (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean um_realm_login_finish (UmRealmObject *realm,
GAsyncResult *result,
GBytes **credentials,
GError **error);
gboolean um_realm_join_as_user (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
G_GNUC_WARN_UNUSED_RESULT;
gboolean um_realm_join_as_admin (UmRealmObject *realm,
const gchar *login,
const gchar *password,
GBytes *credentials,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
G_GNUC_WARN_UNUSED_RESULT;
gboolean um_realm_join_finish (UmRealmObject *realm,
GAsyncResult *result,
GError **error);
gboolean um_realm_is_configured (UmRealmObject *realm);
gchar * um_realm_calculate_login (UmRealmCommon *realm,
const gchar *username);
G_END_DECLS
#endif /* __UM_REALM_H__ */

View file

@ -0,0 +1,389 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <math.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <utmp.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "um-utils.h"
void
set_entry_validation_checkmark (GtkEntry *entry)
{
gtk_entry_set_icon_from_icon_name (entry,
GTK_ENTRY_ICON_SECONDARY,
"object-select-symbolic");
}
void
set_entry_validation_error (GtkEntry *entry,
const gchar *text)
{
gtk_entry_set_icon_from_icon_name (entry,
GTK_ENTRY_ICON_SECONDARY,
"dialog-warning-symbolic");
gtk_entry_set_icon_tooltip_text (entry,
GTK_ENTRY_ICON_SECONDARY,
text);
}
void
clear_entry_validation_error (GtkEntry *entry)
{
gtk_entry_set_icon_from_paintable (entry,
GTK_ENTRY_ICON_SECONDARY,
NULL);
}
#define MAXNAMELEN (UT_NAMESIZE - 1)
static gboolean
is_username_used (const gchar *username)
{
struct passwd *pwent;
if (username == NULL || username[0] == '\0') {
return FALSE;
}
pwent = getpwnam (username);
return pwent != NULL;
}
gboolean
is_valid_name (const gchar *name)
{
gboolean is_empty = TRUE;
const gchar *c;
/* Valid names must contain:
* 1) at least one character.
* 2) at least one non-"space" character.
*/
for (c = name; *c; c++) {
gunichar unichar;
unichar = g_utf8_get_char_validated (c, -1);
/* Partial UTF-8 sequence or end of string */
if (unichar == (gunichar) -1 || unichar == (gunichar) -2)
break;
/* Check for non-space character */
if (!g_unichar_isspace (unichar)) {
is_empty = FALSE;
break;
}
}
return !is_empty;
}
gboolean
is_valid_username (const gchar *username, gboolean parental_controls_enabled, gchar **tip)
{
gboolean empty;
gboolean in_use;
gboolean too_long;
gboolean valid;
gboolean parental_controls_conflict;
const gchar *c;
if (username == NULL || username[0] == '\0') {
empty = TRUE;
in_use = FALSE;
too_long = FALSE;
} else {
empty = FALSE;
in_use = is_username_used (username);
too_long = strlen (username) > MAXNAMELEN;
}
valid = TRUE;
if (!in_use && !empty && !too_long) {
/* First char must be a lower case letter, and it must only be
* composed of lower case letters, digits, '-', and '_'.
*/
for (c = username; *c; c++) {
if (c == username) {
if (! (*c >= 'a' && *c <= 'z'))
valid = FALSE;
} else {
if (! ((*c >= 'a' && *c <= 'z') ||
(*c >= '0' && *c <= '9') ||
(*c == '_') || (*c == '-')))
valid = FALSE;
}
}
}
parental_controls_conflict = (parental_controls_enabled && g_strcmp0 (username, "administrator") == 0);
valid = !empty && !in_use && !too_long && !parental_controls_conflict && valid;
if (!empty && (in_use || too_long || parental_controls_conflict || !valid)) {
if (in_use) {
*tip = g_strdup (_("Sorry, that user name isnt available. Please try another."));
}
else if (too_long) {
*tip = g_strdup_printf (_("The username is too long."));
}
else if (!(username[0] >= 'a' && username[0] <= 'z')) {
*tip = g_strdup (_("The username must start with a lower case letter from a-z."));
}
else if (parental_controls_conflict) {
*tip = g_strdup (_("That username isnt available. Please try another."));
}
else {
*tip = g_strdup (_("The username should only consist of lower case letters from a-z, digits, and the following characters: - _"));
}
}
else {
*tip = g_strdup (_("This will be used to name your home folder and cant be changed."));
}
return valid;
}
void
generate_username_choices (const gchar *name,
GtkListStore *store)
{
gboolean in_use, same_as_initial;
char *lc_name, *ascii_name, *stripped_name;
char **words1;
char **words2 = NULL;
char **w1, **w2;
char *c;
char *unicode_fallback = "?";
GString *first_word, *last_word;
GString *item0, *item1, *item2, *item3, *item4;
int len;
int nwords1, nwords2, i;
GHashTable *items;
GtkTreeIter iter;
gtk_list_store_clear (store);
ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8",
unicode_fallback, NULL, NULL, NULL);
lc_name = g_ascii_strdown (ascii_name, -1);
/* Remove all non ASCII alphanumeric chars from the name,
* apart from the few allowed symbols.
*
* We do remove '.', even though it is usually allowed,
* since it often comes in via an abbreviated middle name,
* and the dot looks just wrong in the proposals then.
*/
stripped_name = g_strnfill (strlen (lc_name) + 1, '\0');
i = 0;
for (c = lc_name; *c; c++) {
if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
*c == ' ' || *c == '-' || *c == '_' ||
/* used to track invalid words, removed below */
*c == '?') )
continue;
stripped_name[i] = *c;
i++;
}
if (strlen (stripped_name) == 0) {
g_free (ascii_name);
g_free (lc_name);
g_free (stripped_name);
return;
}
/* we split name on spaces, and then on dashes, so that we can treat
* words linked with dashes the same way, i.e. both fully shown, or
* both abbreviated
*/
words1 = g_strsplit_set (stripped_name, " ", -1);
len = g_strv_length (words1);
/* The default item is a concatenation of all words without ? */
item0 = g_string_sized_new (strlen (stripped_name));
g_free (ascii_name);
g_free (lc_name);
g_free (stripped_name);
/* Concatenate the whole first word with the first letter of each
* word (item1), and the last word with the first letter of each
* word (item2). item3 and item4 are symmetrical respectively to
* item1 and item2.
*
* Constant 5 is the max reasonable number of words we may get when
* splitting on dashes, since we can't guess it at this point,
* and reallocating would be too bad.
*/
item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
/* again, guess at the max size of names */
first_word = g_string_sized_new (20);
last_word = g_string_sized_new (20);
nwords1 = 0;
nwords2 = 0;
for (w1 = words1; *w1; w1++) {
if (strlen (*w1) == 0)
continue;
/* skip words with string '?', most likely resulting
* from failed transliteration to ASCII
*/
if (strstr (*w1, unicode_fallback) != NULL)
continue;
nwords1++; /* count real words, excluding empty string */
item0 = g_string_append (item0, *w1);
words2 = g_strsplit_set (*w1, "-", -1);
/* reset last word if a new non-empty word has been found */
if (strlen (*words2) > 0)
last_word = g_string_set_size (last_word, 0);
for (w2 = words2; *w2; w2++) {
if (strlen (*w2) == 0)
continue;
nwords2++;
/* part of the first "toplevel" real word */
if (nwords1 == 1) {
item1 = g_string_append (item1, *w2);
first_word = g_string_append (first_word, *w2);
}
else {
item1 = g_string_append_unichar (item1,
g_utf8_get_char (*w2));
item3 = g_string_append_unichar (item3,
g_utf8_get_char (*w2));
}
/* not part of the last "toplevel" word */
if (w1 != words1 + len - 1) {
item2 = g_string_append_unichar (item2,
g_utf8_get_char (*w2));
item4 = g_string_append_unichar (item4,
g_utf8_get_char (*w2));
}
/* always save current word so that we have it if last one reveals empty */
last_word = g_string_append (last_word, *w2);
}
g_strfreev (words2);
}
item2 = g_string_append (item2, last_word->str);
item3 = g_string_append (item3, first_word->str);
item4 = g_string_prepend (item4, last_word->str);
items = g_hash_table_new (g_str_hash, g_str_equal);
in_use = is_username_used (item0->str);
if (!in_use && !g_ascii_isdigit (item0->str[0])) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, item0->str, -1);
g_hash_table_insert (items, item0->str, item0->str);
}
in_use = is_username_used (item1->str);
same_as_initial = (g_strcmp0 (item0->str, item1->str) == 0);
if (!same_as_initial && nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, item1->str, -1);
g_hash_table_insert (items, item1->str, item1->str);
}
/* if there's only one word, would be the same as item1 */
if (nwords2 > 1) {
/* add other items */
in_use = is_username_used (item2->str);
if (!in_use && !g_ascii_isdigit (item2->str[0]) &&
!g_hash_table_lookup (items, item2->str)) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, item2->str, -1);
g_hash_table_insert (items, item2->str, item2->str);
}
in_use = is_username_used (item3->str);
if (!in_use && !g_ascii_isdigit (item3->str[0]) &&
!g_hash_table_lookup (items, item3->str)) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, item3->str, -1);
g_hash_table_insert (items, item3->str, item3->str);
}
in_use = is_username_used (item4->str);
if (!in_use && !g_ascii_isdigit (item4->str[0]) &&
!g_hash_table_lookup (items, item4->str)) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, item4->str, -1);
g_hash_table_insert (items, item4->str, item4->str);
}
/* add the last word */
in_use = is_username_used (last_word->str);
if (!in_use && !g_ascii_isdigit (last_word->str[0]) &&
!g_hash_table_lookup (items, last_word->str)) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, last_word->str, -1);
g_hash_table_insert (items, last_word->str, last_word->str);
}
/* ...and the first one */
in_use = is_username_used (first_word->str);
if (!in_use && !g_ascii_isdigit (first_word->str[0]) &&
!g_hash_table_lookup (items, first_word->str)) {
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, first_word->str, -1);
g_hash_table_insert (items, first_word->str, first_word->str);
}
}
g_hash_table_destroy (items);
g_strfreev (words1);
g_string_free (first_word, TRUE);
g_string_free (last_word, TRUE);
g_string_free (item0, TRUE);
g_string_free (item1, TRUE);
g_string_free (item2, TRUE);
g_string_free (item3, TRUE);
g_string_free (item4, TRUE);
}

View file

@ -0,0 +1,50 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __UM_UTILS_H__
#define __UM_UTILS_H__
#include <gtk/gtk.h>
G_BEGIN_DECLS
void set_entry_validation_error (GtkEntry *entry,
const gchar *text);
void set_entry_validation_checkmark (GtkEntry *entry);
void clear_entry_validation_error (GtkEntry *entry);
gboolean is_valid_name (const gchar *name);
gboolean is_valid_username (const gchar *name,
gboolean parental_controls_enabled,
gchar **tip);
void generate_username_choices (const gchar *name,
GtkListStore *store);
cairo_surface_t *generate_user_picture (const gchar *name);
GdkPixbuf *round_image (GdkPixbuf *image);
G_END_DECLS
#endif

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2013 Red Hat, Inc
*
* 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#ifdef HAVE_IBUS
#include "cc-ibus-utils.h"
gchar *
engine_get_display_name (IBusEngineDesc *engine_desc)
{
const gchar *name;
const gchar *language_code;
const gchar *language;
const gchar *textdomain;
gchar *display_name;
name = ibus_engine_desc_get_longname (engine_desc);
language_code = ibus_engine_desc_get_language (engine_desc);
language = ibus_get_language_name (language_code);
textdomain = ibus_engine_desc_get_textdomain (engine_desc);
if (*textdomain != '\0' && *name != '\0')
name = g_dgettext (textdomain, name);
display_name = g_strdup_printf ("%s (%s)", language, name);
return display_name;
}
#endif /* HAVE_IBUS */

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2013 Red Hat, Inc
*
* 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GIS_IBUS_UTILS_H__
#define __GIS_IBUS_UTILS_H__
#include <ibus.h>
G_BEGIN_DECLS
gchar *engine_get_display_name (IBusEngineDesc *engine_desc);
G_END_DECLS
#endif /* __GIS_IBUS_UTILS_H__ */

View file

@ -0,0 +1,908 @@
/*
* Copyright (C) 2013 Red Hat, Inc.
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
* Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "cc-input-chooser.h"
#include <locale.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-languages.h>
#include <libgnome-desktop/gnome-xkb-info.h>
#ifdef HAVE_IBUS
#include <ibus.h>
#include "cc-ibus-utils.h"
#endif
#include "cc-common-language.h"
#include <glib-object.h>
#define INPUT_SOURCE_TYPE_XKB "xkb"
#define INPUT_SOURCE_TYPE_IBUS "ibus"
#define MIN_ROWS 5
struct _CcInputChooserPrivate
{
GtkWidget *filter_entry;
GtkWidget *input_list;
GPtrArray *input_widget_boxes;
GHashTable *inputs;
GtkWidget *no_results;
GtkWidget *more_item;
gboolean showing_extra;
gchar *locale;
gchar *id;
gchar *type;
GnomeXkbInfo *xkb_info;
#ifdef HAVE_IBUS
IBusBus *ibus;
GHashTable *ibus_engines;
GCancellable *ibus_cancellable;
#endif
};
typedef struct _CcInputChooserPrivate CcInputChooserPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (CcInputChooser, cc_input_chooser, GTK_TYPE_BOX);
enum {
PROP_0,
PROP_SHOWING_EXTRA,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST];
enum {
CHANGED,
CONFIRM,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
GtkWidget *box;
GtkWidget *label;
GtkWidget *checkmark;
gchar *id;
gchar *type;
gchar *name;
gboolean is_extra;
} InputWidget;
/*
* Invariant: for each box in priv->input_widget_boxes,
* get_input_widget (row) is non-null and
* get_input_widget (row)->box == box
*/
static InputWidget *
get_input_widget (GtkWidget *widget)
{
return g_object_get_data (G_OBJECT (widget), "input-widget");
}
static GtkWidget *
padded_label_new (char *text)
{
GtkWidget *widget;
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
gtk_widget_set_margin_top (widget, 10);
gtk_widget_set_margin_bottom (widget, 10);
gtk_box_append (GTK_BOX (widget), gtk_label_new (text));
return widget;
}
static void
input_widget_free (gpointer data)
{
InputWidget *widget = data;
g_free (widget->id);
g_free (widget->type);
g_free (widget->name);
g_free (widget);
}
static gboolean
get_layout (CcInputChooser *chooser,
const gchar *type,
const gchar *id,
const gchar **layout,
const gchar **variant)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
if (g_strcmp0 (type, INPUT_SOURCE_TYPE_XKB) == 0) {
gnome_xkb_info_get_layout_info (priv->xkb_info,
id, NULL, NULL,
layout, variant);
return TRUE;
}
#ifdef HAVE_IBUS
if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) == 0) {
IBusEngineDesc *engine_desc = NULL;
if (priv->ibus_engines)
engine_desc = g_hash_table_lookup (priv->ibus_engines, id);
if (!engine_desc)
return FALSE;
*layout = ibus_engine_desc_get_layout (engine_desc);
*variant = "";
return TRUE;
}
#endif
return FALSE;
}
static gboolean
preview_cb (GtkLabel *label,
const gchar *uri,
CcInputChooser *chooser)
{
GtkWidget *row;
InputWidget *widget;
const gchar *layout;
const gchar *variant;
gchar *commandline;
row = gtk_widget_get_parent (GTK_WIDGET (label));
widget = get_input_widget (row);
if (!get_layout (chooser, widget->type, widget->id, &layout, &variant))
return TRUE;
if (variant[0])
commandline = g_strdup_printf ("tecla \"%s+%s\"", layout, variant);
else
commandline = g_strdup_printf ("tecla %s", layout);
g_spawn_command_line_async (commandline, NULL);
g_free (commandline);
return TRUE;
}
static GtkWidget *
input_widget_new (CcInputChooser *chooser,
const char *type,
const char *id,
gboolean is_extra)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
GtkWidget *label;
InputWidget *widget = g_new0 (InputWidget, 1);
gchar *text;
if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB)) {
const gchar *name;
gnome_xkb_info_get_layout_info (priv->xkb_info, id, &name, NULL, NULL, NULL);
widget->name = g_strdup (name);
}
#ifdef HAVE_IBUS
else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS)) {
if (priv->ibus_engines)
widget->name = engine_get_display_name (g_hash_table_lookup (priv->ibus_engines, id));
else
widget->name = g_strdup (id);
}
#endif
else {
widget->name = g_strdup ("ERROR");
}
widget->id = g_strdup (id);
widget->type = g_strdup (type);
widget->is_extra = is_extra;
widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_top (widget->box, 12);
gtk_widget_set_margin_bottom (widget->box, 12);
gtk_widget_set_margin_start (widget->box, 12);
gtk_widget_set_margin_end (widget->box, 12);
gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL);
widget->label = gtk_label_new (widget->name);
gtk_label_set_ellipsize (GTK_LABEL (widget->label), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars (GTK_LABEL (widget->label), 40);
gtk_label_set_xalign (GTK_LABEL (widget->label), 0);
gtk_box_append (GTK_BOX (widget->box), widget->label);
widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
gtk_box_append (GTK_BOX (widget->box), widget->checkmark);
text = g_strdup_printf ("<a href='preview'>%s</a>", _("Preview"));
label = gtk_label_new ("");
gtk_label_set_markup (GTK_LABEL (label), text);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_set_halign (label, GTK_ALIGN_END);
g_free (text);
g_signal_connect (label, "activate-link",
G_CALLBACK (preview_cb), chooser);
gtk_box_append (GTK_BOX (widget->box), label);
g_object_set_data_full (G_OBJECT (widget->box), "input-widget", widget,
input_widget_free);
return widget->box;
}
static void
sync_all_checkmarks (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv;
gboolean invalidate = FALSE;
gsize i;
priv = cc_input_chooser_get_instance_private (chooser);
for (i = 0; i < priv->input_widget_boxes->len; i++) {
InputWidget *widget;
GtkWidget *child;
gboolean should_be_visible;
child = g_ptr_array_index (priv->input_widget_boxes, i);
widget = get_input_widget (child);
g_assert (widget != NULL);
g_assert (widget->box == child);
if (priv->id == NULL || priv->type == NULL)
should_be_visible = FALSE;
else
should_be_visible = g_strcmp0 (widget->id, priv->id) == 0 &&
g_strcmp0 (widget->type, priv->type) == 0;
gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0);
if (widget->is_extra && should_be_visible) {
g_debug ("Marking selected layout %s (%s:%s) as non-extra",
widget->name, widget->type, widget->id);
widget->is_extra = FALSE;
invalidate = TRUE;
}
}
if (invalidate) {
gtk_list_box_invalidate_sort (GTK_LIST_BOX (priv->input_list));
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
}
}
static GtkWidget *
more_widget_new (void)
{
GtkWidget *widget;
GtkWidget *arrow;
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_widget_set_tooltip_text (widget, _("More…"));
arrow = gtk_image_new_from_icon_name ("view-more-symbolic");
gtk_widget_add_css_class (arrow, "dim-label");
gtk_widget_set_margin_top (widget, 12);
gtk_widget_set_margin_bottom (widget, 12);
gtk_widget_set_hexpand (arrow, TRUE);
gtk_widget_set_halign (arrow, GTK_ALIGN_CENTER);
gtk_widget_set_valign (arrow, GTK_ALIGN_CENTER);
gtk_box_append (GTK_BOX (widget), arrow);
return widget;
}
static GtkWidget *
no_results_widget_new (void)
{
GtkWidget *widget;
/* Translators: a search for input methods or keyboard layouts
* did not yield any results
*/
widget = padded_label_new (_("No inputs found"));
gtk_widget_set_sensitive (widget, FALSE);
return widget;
}
static void
choose_non_extras (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv;
guint count = 0;
gsize i;
priv = cc_input_chooser_get_instance_private (chooser);
for (i = 0; i < priv->input_widget_boxes->len; i++) {
InputWidget *widget;
GtkWidget *child;
if (++count > MIN_ROWS)
break;
child = g_ptr_array_index (priv->input_widget_boxes, i);
widget = get_input_widget (child);
g_assert (widget != NULL);
g_assert (widget->box == child);
g_debug ("Picking %s (%s:%s) as non-extra",
widget->name, widget->type, widget->id);
widget->is_extra = FALSE;
}
/* Changing is_extra above affects the ordering and the visibility
* of the newly non-extra rows.
*/
gtk_list_box_invalidate_sort (GTK_LIST_BOX (priv->input_list));
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
}
static void
add_rows_to_list (CcInputChooser *chooser,
GList *list,
const gchar *type,
const gchar *default_id)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
const gchar *id;
GtkWidget *widget;
gchar *key;
for (; list; list = list->next) {
id = (const gchar *) list->data;
if (g_strcmp0 (id, default_id) == 0)
continue;
key = g_strdup_printf ("%s::%s", type, id);
if (g_hash_table_contains (priv->inputs, key)) {
g_free (key);
continue;
}
g_hash_table_add (priv->inputs, key);
widget = input_widget_new (chooser, type, id, TRUE);
g_ptr_array_add (priv->input_widget_boxes, g_object_ref_sink (widget));
gtk_list_box_append (GTK_LIST_BOX (priv->input_list), widget);
}
}
static void
add_row_to_list (CcInputChooser *chooser,
const gchar *type,
const gchar *id)
{
GList tmp = { 0 };
tmp.data = (gpointer)id;
add_rows_to_list (chooser, &tmp, type, NULL);
}
static void
get_locale_infos (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
const gchar *type = NULL;
const gchar *id = NULL;
g_autofree gchar *lang = NULL;
g_autofree gchar *country = NULL;
GList *list;
if (gnome_get_input_source_from_locale (priv->locale, &type, &id)) {
add_row_to_list (chooser, type, id);
if (!priv->id) {
priv->id = g_strdup (id);
priv->type = g_strdup (type);
}
}
if (!gnome_parse_locale (priv->locale, &lang, &country, NULL, NULL))
return;
list = gnome_xkb_info_get_layouts_for_language (priv->xkb_info, lang);
add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
g_list_free (list);
if (country != NULL) {
list = gnome_xkb_info_get_layouts_for_country (priv->xkb_info, country);
add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
g_list_free (list);
}
choose_non_extras (chooser);
list = gnome_xkb_info_get_all_layouts (priv->xkb_info);
add_rows_to_list (chooser, list, INPUT_SOURCE_TYPE_XKB, id);
g_list_free (list);
}
static gboolean
input_visible (GtkListBoxRow *row,
gpointer user_data)
{
CcInputChooser *chooser = user_data;
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
InputWidget *widget;
gboolean visible;
GtkWidget *child;
const char *search_term;
child = gtk_list_box_row_get_child (row);
if (child == priv->more_item)
return !priv->showing_extra && g_hash_table_size (priv->inputs) > MIN_ROWS;
widget = get_input_widget (child);
if (!priv->showing_extra && widget->is_extra)
return FALSE;
search_term = gtk_editable_get_text (GTK_EDITABLE (priv->filter_entry));
if (!search_term || !*search_term)
return TRUE;
visible = g_str_match_string (search_term, widget->name, TRUE);
return visible;
}
static gint
sort_inputs (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer data)
{
InputWidget *la, *lb;
la = get_input_widget (gtk_list_box_row_get_child (a));
lb = get_input_widget (gtk_list_box_row_get_child (b));
if (la == NULL)
return 1;
if (lb == NULL)
return -1;
if (la->is_extra && !lb->is_extra)
return 1;
if (!la->is_extra && lb->is_extra)
return -1;
return strcmp (la->name, lb->name);
}
static void
filter_changed (GtkEntry *entry,
CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
}
static void
show_more (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
if (g_hash_table_size (priv->inputs) <= MIN_ROWS)
return;
gtk_widget_grab_focus (priv->filter_entry);
gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL);
priv->showing_extra = TRUE;
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]);
}
static void
set_input (CcInputChooser *chooser,
const gchar *id,
const gchar *type)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
if (g_strcmp0 (priv->id, id) == 0 &&
g_strcmp0 (priv->type, type) == 0)
return;
g_free (priv->id);
g_free (priv->type);
priv->id = g_strdup (id);
priv->type = g_strdup (type);
sync_all_checkmarks (chooser);
g_signal_emit (chooser, signals[CHANGED], 0);
}
static gboolean
confirm_choice (gpointer data)
{
GtkWidget *widget = data;
g_signal_emit (widget, signals[CONFIRM], 0);
return G_SOURCE_REMOVE;
}
static void
row_activated (GtkListBox *box,
GtkListBoxRow *row,
CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
GtkWidget *child;
InputWidget *widget;
if (row == NULL)
return;
child = gtk_list_box_row_get_child (row);
if (child == priv->more_item) {
show_more (chooser);
} else {
widget = get_input_widget (child);
if (widget == NULL)
return;
if (g_strcmp0 (priv->id, widget->id) == 0 &&
g_strcmp0 (priv->type, widget->type) == 0)
confirm_choice (chooser);
else
set_input (chooser, widget->id, widget->type);
}
}
#ifdef HAVE_IBUS
static void
update_ibus_active_sources (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv;
gboolean invalidate = FALSE;
IBusEngineDesc *engine_desc;
const gchar *type;
const gchar *id;
gchar *name;
gsize i;
priv = cc_input_chooser_get_instance_private (chooser);
for (i = 0; i < priv->input_widget_boxes->len; i++) {
GtkWidget *child;
InputWidget *row;
child = g_ptr_array_index (priv->input_widget_boxes, i);
row = get_input_widget (child);
g_assert (row != NULL);
g_assert (row->box == child);
type = row->type;
id = row->id;
if (g_strcmp0 (type, INPUT_SOURCE_TYPE_IBUS) != 0)
continue;
engine_desc = g_hash_table_lookup (priv->ibus_engines, id);
if (engine_desc) {
name = engine_get_display_name (engine_desc);
gtk_label_set_text (GTK_LABEL (row->label), name);
g_clear_pointer (&row->name, g_free);
row->name = g_steal_pointer (&name);
invalidate = TRUE;
}
}
if (invalidate) {
gtk_list_box_invalidate_sort (GTK_LIST_BOX (priv->input_list));
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->input_list));
}
}
static void
get_ibus_locale_infos (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
GHashTableIter iter;
const gchar *engine_id;
IBusEngineDesc *engine;
if (!priv->ibus_engines)
return;
g_hash_table_iter_init (&iter, priv->ibus_engines);
while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine))
add_row_to_list (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
static void
fetch_ibus_engines_result (GObject *object,
GAsyncResult *result,
CcInputChooser *chooser)
{
CcInputChooserPrivate *priv;
GList *list, *l;
GError *error;
error = NULL;
list = ibus_bus_list_engines_async_finish (IBUS_BUS (object), result, &error);
if (!list && error) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Couldn't finish IBus request: %s", error->message);
g_error_free (error);
return;
}
priv = cc_input_chooser_get_instance_private (chooser);
g_clear_object (&priv->ibus_cancellable);
/* Maps engine ids to engine description objects */
priv->ibus_engines = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
for (l = list; l; l = l->next) {
IBusEngineDesc *engine = l->data;
const gchar *engine_id;
engine_id = ibus_engine_desc_get_name (engine);
if (g_str_has_prefix (engine_id, "xkb:"))
g_object_unref (engine);
else
g_hash_table_replace (priv->ibus_engines, (gpointer)engine_id, engine);
}
g_list_free (list);
update_ibus_active_sources (chooser);
get_ibus_locale_infos (chooser);
sync_all_checkmarks (chooser);
}
static void
fetch_ibus_engines (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
priv->ibus_cancellable = g_cancellable_new ();
ibus_bus_list_engines_async (priv->ibus,
-1,
priv->ibus_cancellable,
(GAsyncReadyCallback)fetch_ibus_engines_result,
chooser);
/* We've got everything we needed, don't want to be called again. */
g_signal_handlers_disconnect_by_func (priv->ibus, fetch_ibus_engines, chooser);
}
static void
maybe_start_ibus (void)
{
/* IBus doesn't export API in the session bus. The only thing
* we have there is a well known name which we can use as a
* sure-fire way to activate it.
*/
g_bus_unwatch_name (g_bus_watch_name (G_BUS_TYPE_SESSION,
IBUS_SERVICE_IBUS,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
NULL,
NULL,
NULL,
NULL));
}
#endif
static void
cc_input_chooser_constructed (GObject *object)
{
CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
G_OBJECT_CLASS (cc_input_chooser_parent_class)->constructed (object);
priv->xkb_info = gnome_xkb_info_new ();
#ifdef HAVE_IBUS
ibus_init ();
if (!priv->ibus) {
priv->ibus = ibus_bus_new_async ();
if (ibus_bus_is_connected (priv->ibus))
fetch_ibus_engines (chooser);
else
g_signal_connect_swapped (priv->ibus, "connected",
G_CALLBACK (fetch_ibus_engines), chooser);
}
maybe_start_ibus ();
#endif
priv->inputs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
priv->more_item = more_widget_new ();
priv->no_results = no_results_widget_new ();
gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->input_list),
sort_inputs, chooser, NULL);
gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->input_list),
input_visible, chooser, NULL);
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->input_list),
GTK_SELECTION_NONE);
if (priv->locale == NULL) {
priv->locale = cc_common_language_get_current_language ();
}
get_locale_infos (chooser);
#ifdef HAVE_IBUS
get_ibus_locale_infos (chooser);
#endif
gtk_list_box_append (GTK_LIST_BOX (priv->input_list), priv->more_item);
gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->input_list), priv->no_results);
g_signal_connect (priv->filter_entry, "changed",
G_CALLBACK (filter_changed),
chooser);
g_signal_connect (priv->input_list, "row-activated",
G_CALLBACK (row_activated), chooser);
sync_all_checkmarks (chooser);
}
static void
cc_input_chooser_finalize (GObject *object)
{
CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
g_clear_object (&priv->xkb_info);
g_hash_table_unref (priv->inputs);
g_clear_pointer (&priv->input_widget_boxes, g_ptr_array_unref);
#ifdef HAVE_IBUS
g_signal_handlers_disconnect_by_func (priv->ibus, fetch_ibus_engines, chooser);
g_clear_object (&priv->ibus);
if (priv->ibus_cancellable)
g_cancellable_cancel (priv->ibus_cancellable);
g_clear_object (&priv->ibus_cancellable);
g_clear_pointer (&priv->ibus_engines, g_hash_table_destroy);
#endif
G_OBJECT_CLASS (cc_input_chooser_parent_class)->finalize (object);
}
static void
cc_input_chooser_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcInputChooser *chooser = CC_INPUT_CHOOSER (object);
switch (prop_id) {
case PROP_SHOWING_EXTRA:
g_value_set_boolean (value, cc_input_chooser_get_showing_extra (chooser));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
cc_input_chooser_class_init (CcInputChooserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/input-chooser.ui");
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, filter_entry);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcInputChooser, input_list);
object_class->finalize = cc_input_chooser_finalize;
object_class->get_property = cc_input_chooser_get_property;
object_class->constructed = cc_input_chooser_constructed;
obj_props[PROP_SHOWING_EXTRA] =
g_param_spec_string ("showing-extra", "", "", "",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[CONFIRM] =
g_signal_new ("confirm",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
}
static void
cc_input_chooser_init (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
gtk_widget_init_template (GTK_WIDGET (chooser));
priv->input_widget_boxes = g_ptr_array_new_with_free_func (g_object_unref);
}
void
cc_input_chooser_clear_filter (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
gtk_editable_set_text (GTK_EDITABLE (priv->filter_entry), "");
}
const gchar *
cc_input_chooser_get_input_id (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
return priv->id;
}
const gchar *
cc_input_chooser_get_input_type (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
return priv->type;
}
void
cc_input_chooser_get_layout (CcInputChooser *chooser,
const gchar **layout,
const gchar **variant)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
if (!get_layout (chooser, priv->type, priv->id, layout, variant)) {
if (layout != NULL)
*layout = NULL;
if (variant != NULL)
*variant = NULL;
}
}
void
cc_input_chooser_set_input (CcInputChooser *chooser,
const gchar *id,
const gchar *type)
{
set_input (chooser, id, type);
}
gboolean
cc_input_chooser_get_showing_extra (CcInputChooser *chooser)
{
CcInputChooserPrivate *priv = cc_input_chooser_get_instance_private (chooser);
return priv->showing_extra;
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2013 Red Hat, Inc
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
* Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __GIS_INPUT_CHOOSER_H__
#define __GIS_INPUT_CHOOSER_H__
#include <gtk/gtk.h>
#include <glib-object.h>
#define CC_TYPE_INPUT_CHOOSER (cc_input_chooser_get_type ())
#define CC_INPUT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_INPUT_CHOOSER, CcInputChooser))
#define CC_INPUT_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_INPUT_CHOOSER, CcInputChooserClass))
#define CC_IS_INPUT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_INPUT_CHOOSER))
#define CC_IS_INPUT_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_INPUT_CHOOSER))
#define CC_INPUT_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_INPUT_CHOOSER, CcInputChooserClass))
G_BEGIN_DECLS
typedef struct _CcInputChooser CcInputChooser;
typedef struct _CcInputChooserClass CcInputChooserClass;
struct _CcInputChooser
{
GtkBox parent;
};
struct _CcInputChooserClass
{
GtkBoxClass parent_class;
};
GType cc_input_chooser_get_type (void);
void cc_input_chooser_clear_filter (CcInputChooser *chooser);
const gchar * cc_input_chooser_get_input_id (CcInputChooser *chooser);
const gchar * cc_input_chooser_get_input_type (CcInputChooser *chooser);
void cc_input_chooser_set_input (CcInputChooser *chooser,
const gchar *id,
const gchar *type);
void cc_input_chooser_get_layout (CcInputChooser *chooser,
const gchar **layout,
const gchar **variant);
gboolean cc_input_chooser_get_showing_extra (CcInputChooser *chooser);
G_END_DECLS
#endif /* __GIS_INPUT_CHOOSER_H__ */

View file

@ -0,0 +1,538 @@
/*
* Copyright (C) 2010 Intel, Inc
*
* 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 2 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/>.
*
* Author: Sergey Udaltsov <svu@gnome.org>
* Michael Wood <michael.g.wood@intel.com>
*
* Based on gnome-control-center cc-region-panel.c
*/
#define PAGE_ID "keyboard"
#include "config.h"
#include <locale.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <polkit/polkit.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-languages.h>
#include "gis-keyboard-page.h"
#include "keyboard-resources.h"
#include "cc-input-chooser.h"
#include "cc-common-language.h"
#include "gis-page-header.h"
#define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
#define KEY_CURRENT_INPUT_SOURCE "current"
#define KEY_INPUT_SOURCES "sources"
struct _GisKeyboardPagePrivate {
GtkWidget *input_chooser;
GDBusProxy *localed;
GCancellable *cancellable;
GPermission *permission;
GSettings *input_settings;
GSList *system_sources;
};
typedef struct _GisKeyboardPagePrivate GisKeyboardPagePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GisKeyboardPage, gis_keyboard_page, GIS_TYPE_PAGE);
static void
gis_keyboard_page_finalize (GObject *object)
{
GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object);
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
if (priv->cancellable)
g_cancellable_cancel (priv->cancellable);
g_clear_object (&priv->cancellable);
g_clear_object (&priv->permission);
g_clear_object (&priv->localed);
g_clear_object (&priv->input_settings);
g_slist_free_full (priv->system_sources, g_free);
G_OBJECT_CLASS (gis_keyboard_page_parent_class)->finalize (object);
}
static void
set_input_settings (GisKeyboardPage *self)
{
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
const gchar *type;
const gchar *id;
GVariantBuilder builder;
GSList *l;
gboolean is_xkb_source = FALSE;
type = cc_input_chooser_get_input_type (CC_INPUT_CHOOSER (priv->input_chooser));
id = cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser));
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
if (g_str_equal (type, "xkb")) {
g_variant_builder_add (&builder, "(ss)", type, id);
is_xkb_source = TRUE;
}
for (l = priv->system_sources; l; l = l->next) {
const gchar *sid = l->data;
if (g_str_equal (id, sid) && g_str_equal (type, "xkb"))
continue;
g_variant_builder_add (&builder, "(ss)", "xkb", sid);
}
if (!is_xkb_source)
g_variant_builder_add (&builder, "(ss)", type, id);
g_settings_set_value (priv->input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
g_settings_set_uint (priv->input_settings, KEY_CURRENT_INPUT_SOURCE, 0);
g_settings_apply (priv->input_settings);
}
static void
set_localed_input (GisKeyboardPage *self)
{
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
const gchar *layout, *variant;
GString *layouts;
GString *variants;
GSList *l;
if (!priv->localed)
return;
cc_input_chooser_get_layout (CC_INPUT_CHOOSER (priv->input_chooser), &layout, &variant);
if (layout == NULL)
layout = "";
if (variant == NULL)
variant = "";
layouts = g_string_new (layout);
variants = g_string_new (variant);
#define LAYOUT(a) (a[0])
#define VARIANT(a) (a[1] ? a[1] : "")
for (l = priv->system_sources; l; l = l->next) {
const gchar *sid = l->data;
gchar **lv = g_strsplit (sid, "+", -1);
if (!g_str_equal (LAYOUT (lv), layout) ||
!g_str_equal (VARIANT (lv), variant)) {
if (layouts->str[0]) {
g_string_append_c (layouts, ',');
g_string_append_c (variants, ',');
}
g_string_append (layouts, LAYOUT (lv));
g_string_append (variants, VARIANT (lv));
}
g_strfreev (lv);
}
#undef LAYOUT
#undef VARIANT
g_dbus_proxy_call (priv->localed,
"SetX11Keyboard",
g_variant_new ("(ssssbb)", layouts->str, "", variants->str, "", TRUE, TRUE),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL, NULL);
g_string_free (layouts, TRUE);
g_string_free (variants, TRUE);
}
static void
change_locale_permission_acquired (GObject *source,
GAsyncResult *res,
gpointer data)
{
GisKeyboardPage *page = GIS_KEYBOARD_PAGE (data);
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (page);
GError *error = NULL;
gboolean allowed;
allowed = g_permission_acquire_finish (priv->permission, res, &error);
if (error) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to acquire permission: %s", error->message);
g_error_free (error);
return;
}
if (allowed)
set_localed_input (GIS_KEYBOARD_PAGE (data));
}
static void
update_input (GisKeyboardPage *self)
{
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
set_input_settings (self);
if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER) {
if (g_permission_get_allowed (priv->permission)) {
set_localed_input (self);
} else if (g_permission_get_can_acquire (priv->permission)) {
g_permission_acquire_async (priv->permission,
NULL,
change_locale_permission_acquired,
self);
}
}
}
static gboolean
gis_keyboard_page_apply (GisPage *page,
GCancellable *cancellable)
{
update_input (GIS_KEYBOARD_PAGE (page));
return FALSE;
}
static GSList *
get_localed_input (GDBusProxy *proxy)
{
GVariant *v;
const gchar *s;
gchar *id;
guint i, n;
gchar **layouts = NULL;
gchar **variants = NULL;
GSList *sources = NULL;
v = g_dbus_proxy_get_cached_property (proxy, "X11Layout");
if (v) {
s = g_variant_get_string (v, NULL);
layouts = g_strsplit (s, ",", -1);
g_variant_unref (v);
}
v = g_dbus_proxy_get_cached_property (proxy, "X11Variant");
if (v) {
s = g_variant_get_string (v, NULL);
if (s && *s)
variants = g_strsplit (s, ",", -1);
g_variant_unref (v);
}
if (variants && variants[0])
n = MIN (g_strv_length (layouts), g_strv_length (variants));
else if (layouts && layouts[0])
n = g_strv_length (layouts);
else
n = 0;
for (i = 0; i < n && layouts[i][0]; i++) {
if (variants && variants[i] && variants[i][0])
id = g_strdup_printf ("%s+%s", layouts[i], variants[i]);
else
id = g_strdup (layouts[i]);
sources = g_slist_prepend (sources, id);
}
g_strfreev (variants);
g_strfreev (layouts);
return sources;
}
static void
add_default_keyboard_layout (GDBusProxy *proxy,
GVariantBuilder *builder)
{
GSList *sources = get_localed_input (proxy);
sources = g_slist_reverse (sources);
for (; sources; sources = sources->next)
g_variant_builder_add (builder, "(ss)", "xkb",
(const gchar *) sources->data);
g_slist_free_full (sources, g_free);
}
static void
add_default_input_sources (GisKeyboardPage *self,
GDBusProxy *proxy)
{
const gchar *type;
const gchar *id;
gchar *language;
GVariantBuilder builder;
GSettings *input_settings;
input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)"));
add_default_keyboard_layout (proxy, &builder);
/* add other input sources */
language = cc_common_language_get_current_language ();
if (gnome_get_input_source_from_locale (language, &type, &id)) {
if (!g_str_equal (type, "xkb"))
g_variant_builder_add (&builder, "(ss)", type, id);
}
g_free (language);
g_settings_delay (input_settings);
g_settings_set_value (input_settings, KEY_INPUT_SOURCES, g_variant_builder_end (&builder));
g_settings_set_uint (input_settings, KEY_CURRENT_INPUT_SOURCE, 0);
g_settings_apply (input_settings);
g_object_unref (input_settings);
}
static void
skip_proxy_ready (GObject *source,
GAsyncResult *res,
gpointer data)
{
GisKeyboardPage *self = data;
GDBusProxy *proxy;
GError *error = NULL;
proxy = g_dbus_proxy_new_finish (res, &error);
if (!proxy) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to contact localed: %s", error->message);
g_error_free (error);
return;
}
add_default_input_sources (self, proxy);
g_object_unref (proxy);
}
static void
gis_keyboard_page_skip (GisPage *page)
{
GisKeyboardPage *self = GIS_KEYBOARD_PAGE (page);
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
NULL,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
priv->cancellable,
(GAsyncReadyCallback) skip_proxy_ready,
self);
}
static void
preselect_input_source (GisKeyboardPage *self)
{
const gchar *type;
const gchar *id;
gchar *language;
gboolean desktop_got_something;
gboolean desktop_got_input_method;
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
GSList *sources = get_localed_input (priv->localed);
/* These will be added silently after the user selection when
* writing out the settings. */
g_slist_free_full (priv->system_sources, g_free);
priv->system_sources = g_slist_reverse (sources);
/* We have two potential sources of information as to which
* source to pre-select here: the keyboard layout that is
* configured system-wide (read from priv->system_sources),
* and a gnome-desktop function that lets us look up a default
* input source for a given language.
*
* An important limitation here is that there is no system-wide
* configuration for input methods, so if the best choice for the
* language is an input method, we will only find it from the
* gnome-desktop lookup. But if both sources give us keyboard layouts,
* we want to prefer the one that's configured system-wide over the one
* from gnome-desktop.
*
* So we first do the gnome-desktop lookup, and keep track of what we
* got.
*
* - If we got an input method, we preselect that, and we're done.
* - If we got a keyboard layout, and there's no system-wide keyboard
* layout set, we preselect the layout we got from gnome-desktop.
* - If we didn't get an input method from gnome-desktop and there
* is a system-wide keyboard layout set, we preselect that.
* - If we got nothing from gnome-desktop and there's no system-wide
* keyboard layout set, we don't preselect anything.
*
* See:
* - https://bugzilla.gnome.org/show_bug.cgi?id=776189
* - https://gitlab.gnome.org/GNOME/gnome-initial-setup/-/issues/104
*/
language = cc_common_language_get_current_language ();
desktop_got_something = gnome_get_input_source_from_locale (language, &type, &id);
desktop_got_input_method = (desktop_got_something && g_strcmp0 (type, "xkb") != 0);
if (desktop_got_something && (desktop_got_input_method || !priv->system_sources)) {
cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser),
id, type);
} else if (priv->system_sources) {
cc_input_chooser_set_input (CC_INPUT_CHOOSER (priv->input_chooser),
(const gchar *) priv->system_sources->data,
"xkb");
}
g_free (language);
}
static void
update_page_complete (GisKeyboardPage *self)
{
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
gboolean complete;
complete = (priv->localed != NULL &&
cc_input_chooser_get_input_id (CC_INPUT_CHOOSER (priv->input_chooser)) != NULL);
gis_page_set_complete (GIS_PAGE (self), complete);
}
static void
localed_proxy_ready (GObject *source,
GAsyncResult *res,
gpointer data)
{
GisKeyboardPage *self = data;
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
GDBusProxy *proxy;
GError *error = NULL;
proxy = g_dbus_proxy_new_finish (res, &error);
if (!proxy) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to contact localed: %s", error->message);
g_error_free (error);
return;
}
priv->localed = proxy;
preselect_input_source (self);
update_page_complete (self);
}
static void
input_confirmed (CcInputChooser *chooser,
GisKeyboardPage *self)
{
gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (self)->driver));
}
static void
input_changed (CcInputChooser *chooser,
GisKeyboardPage *self)
{
update_page_complete (self);
}
static void
gis_keyboard_page_constructed (GObject *object)
{
GisKeyboardPage *self = GIS_KEYBOARD_PAGE (object);
GisKeyboardPagePrivate *priv = gis_keyboard_page_get_instance_private (self);
G_OBJECT_CLASS (gis_keyboard_page_parent_class)->constructed (object);
g_signal_connect (priv->input_chooser, "confirm",
G_CALLBACK (input_confirmed), self);
g_signal_connect (priv->input_chooser, "changed",
G_CALLBACK (input_changed), self);
priv->input_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
g_settings_delay (priv->input_settings);
priv->cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
NULL,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
priv->cancellable,
(GAsyncReadyCallback) localed_proxy_ready,
self);
/* If we're in new user mode then we're manipulating system settings */
if (gis_driver_get_mode (GIS_PAGE (self)->driver) == GIS_DRIVER_MODE_NEW_USER)
priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-keyboard", NULL, NULL, NULL);
update_page_complete (self);
gtk_widget_set_visible (GTK_WIDGET (self), TRUE);
}
static void
gis_keyboard_page_locale_changed (GisPage *page)
{
gis_page_set_title (GIS_PAGE (page), _("Typing"));
}
static void
gis_keyboard_page_class_init (GisKeyboardPageClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GisPageClass * page_class = GIS_PAGE_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-keyboard-page.ui");
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisKeyboardPage, input_chooser);
page_class->page_id = PAGE_ID;
page_class->apply = gis_keyboard_page_apply;
page_class->skip = gis_keyboard_page_skip;
page_class->locale_changed = gis_keyboard_page_locale_changed;
object_class->constructed = gis_keyboard_page_constructed;
object_class->finalize = gis_keyboard_page_finalize;
}
static void
gis_keyboard_page_init (GisKeyboardPage *self)
{
g_type_ensure (GIS_TYPE_PAGE_HEADER);
g_type_ensure (CC_TYPE_INPUT_CHOOSER);
gtk_widget_init_template (GTK_WIDGET (self));
}
GisPage *
gis_prepare_keyboard_page (GisDriver *driver)
{
return g_object_new (GIS_TYPE_KEYBOARD_PAGE,
"driver", driver,
NULL);
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2010 Intel, Inc
*
* 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 2 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/>.
*
* Author: Sergey Udaltsov <svu@gnome.org>
*
*/
#ifndef _GIS_KEYBOARD_PAGE_H
#define _GIS_KEYBOARD_PAGE_H
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
#define GIS_TYPE_KEYBOARD_PAGE gis_keyboard_page_get_type()
#define GIS_KEYBOARD_PAGE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPage))
#define GIS_KEYBOARD_PAGE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPageClass))
#define GIS_IS_KEYBOARD_PAGE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
GIS_TYPE_KEYBOARD_PAGE))
#define GIS_IS_KEYBOARD_PAGE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
GIS_TYPE_KEYBOARD_PAGE))
#define GIS_KEYBOARD_PAGE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
GIS_TYPE_KEYBOARD_PAGE, GisKeyboardPageClass))
typedef struct _GisKeyboardPage GisKeyboardPage;
typedef struct _GisKeyboardPageClass GisKeyboardPageClass;
struct _GisKeyboardPage
{
GisPage parent;
};
struct _GisKeyboardPageClass
{
GisPageClass parent_class;
};
GType gis_keyboard_page_get_type (void) G_GNUC_CONST;
GisPage *gis_prepare_keyboard_page (GisDriver *driver);
G_END_DECLS
#endif /* _GIS_KEYBOARD_PAGE_H */

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GisKeyboardPage" parent="GisPage">
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkBox" id="page">
<property name="orientation">vertical</property>
<child>
<object class="GisPageHeader" id="header">
<property name="margin_top">24</property>
<property name="title" translatable="yes">Typing</property>
<property name="subtitle" translatable="yes">Select your keyboard layout or an input method.</property>
<property name="icon_name">input-keyboard-symbolic</property>
<property name="show_icon" bind-source="GisKeyboardPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/>
</object>
</child>
<child>
<object class="CcInputChooser" id="input_chooser">
<property name="margin_top">18</property>
<property name="margin_bottom">18</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<interface>
<template class="CcInputChooser" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkSearchEntry" id="filter_entry">
<property name="placeholder_text" translatable="yes">Search keyboards and input methods</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkListBox" id="input_list">
<property name="vexpand">True</property>
<property name="halign">fill</property>
<property name="valign">start</property>
<style>
<class name="boxed-list" />
</style>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/initial-setup">
<file preprocess="xml-stripblanks">gis-keyboard-page.ui</file>
<file preprocess="xml-stripblanks">input-chooser.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,14 @@
sources += gnome.compile_resources(
'keyboard-resources',
files('keyboard.gresource.xml'),
c_name: 'keyboard'
)
sources += files(
'cc-input-chooser.c',
'cc-input-chooser.h',
'cc-ibus-utils.c',
'cc-ibus-utils.h',
'gis-keyboard-page.c',
'gis-keyboard-page.h'
)

View file

@ -0,0 +1,597 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
* Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "cc-language-chooser.h"
#include <locale.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-languages.h>
#include "cc-common-language.h"
#include "cc-util.h"
#include <glib-object.h>
struct _CcLanguageChooserPrivate
{
GtkWidget *filter_entry;
GtkWidget *language_list;
GtkWidget *no_results;
GtkWidget *more_item;
gboolean showing_extra;
gchar *language;
};
typedef struct _CcLanguageChooserPrivate CcLanguageChooserPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_BOX);
enum {
PROP_0,
PROP_LANGUAGE,
PROP_SHOWING_EXTRA,
PROP_LAST,
};
static GParamSpec *obj_props[PROP_LAST];
enum {
CONFIRM,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
GtkWidget *box;
GtkWidget *checkmark;
gchar *locale_id;
gchar *locale_name;
gchar *locale_current_name;
gchar *locale_untranslated_name;
gchar *sort_key;
gboolean is_extra;
} LanguageWidget;
static LanguageWidget *
get_language_widget (GtkWidget *widget)
{
return g_object_get_data (G_OBJECT (widget), "language-widget");
}
static GtkWidget *
padded_label_new (char *text)
{
GtkWidget *widget;
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
gtk_widget_set_margin_top (widget, 12);
gtk_widget_set_margin_bottom (widget, 12);
gtk_box_append (GTK_BOX (widget), gtk_label_new (text));
return widget;
}
static void
language_widget_free (gpointer data)
{
LanguageWidget *widget = data;
/* This is called when the box is destroyed,
* so don't bother destroying the widget and
* children again. */
g_free (widget->locale_id);
g_free (widget->locale_name);
g_free (widget->locale_current_name);
g_free (widget->locale_untranslated_name);
g_free (widget->sort_key);
g_free (widget);
}
static GtkWidget *
language_widget_new (const char *locale_id,
gboolean is_extra)
{
GtkWidget *label;
gchar *locale_name, *locale_current_name, *locale_untranslated_name;
gchar *language = NULL;
gchar *language_name;
gchar *country = NULL;
gchar *country_name = NULL;
LanguageWidget *widget = g_new0 (LanguageWidget, 1);
if (!gnome_parse_locale (locale_id, &language, &country, NULL, NULL))
return NULL;
language_name = gnome_get_language_from_code (language, locale_id);
if (language_name == NULL)
language_name = gnome_get_language_from_code (language, NULL);
if (country) {
country_name = gnome_get_country_from_code (country, locale_id);
if (country_name == NULL)
country_name = gnome_get_country_from_code (country, NULL);
}
locale_name = gnome_get_language_from_locale (locale_id, locale_id);
locale_current_name = gnome_get_language_from_locale (locale_id, NULL);
locale_untranslated_name = gnome_get_language_from_locale (locale_id, "C");
widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_top (widget->box, 12);
gtk_widget_set_margin_bottom (widget->box, 12);
gtk_widget_set_margin_start (widget->box, 12);
gtk_widget_set_margin_end (widget->box, 12);
gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL);
label = gtk_label_new (language_name);
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars (GTK_LABEL (label), 30);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_box_append (GTK_BOX (widget->box), label);
widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
gtk_box_append (GTK_BOX (widget->box), widget->checkmark);
if (country_name) {
label = gtk_label_new (country_name);
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars (GTK_LABEL (label), 30);
gtk_widget_add_css_class (label, "dim-label");
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_set_halign (label, GTK_ALIGN_END);
gtk_box_append (GTK_BOX (widget->box), label);
}
widget->locale_id = g_strdup (locale_id);
widget->locale_name = locale_name;
widget->locale_current_name = locale_current_name;
widget->locale_untranslated_name = locale_untranslated_name;
widget->is_extra = is_extra;
widget->sort_key = cc_util_normalize_casefold_and_unaccent (locale_name);
g_object_set_data_full (G_OBJECT (widget->box), "language-widget", widget,
language_widget_free);
g_free (language);
g_free (language_name);
g_free (country);
g_free (country_name);
return widget->box;
}
static void
sync_all_checkmarks (CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
GtkWidget *row;
row = gtk_widget_get_first_child (priv->language_list);
while (row) {
LanguageWidget *widget;
GtkWidget *child;
gboolean should_be_visible;
child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
widget = get_language_widget (child);
if (widget == NULL)
return;
should_be_visible = g_str_equal (widget->locale_id, priv->language);
gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0);
row = gtk_widget_get_next_sibling (row);
}
}
static GtkWidget *
more_widget_new (void)
{
GtkWidget *widget;
GtkWidget *arrow;
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_tooltip_text (widget, _("More…"));
arrow = g_object_new (GTK_TYPE_IMAGE,
"icon-name", "view-more-symbolic",
"hexpand", TRUE,
"halign", GTK_ALIGN_CENTER,
NULL);
gtk_widget_add_css_class (arrow, "dim-label");
gtk_widget_set_margin_top (widget, 12);
gtk_widget_set_margin_bottom (widget, 12);
gtk_box_append (GTK_BOX (widget), arrow);
return widget;
}
static GtkWidget *
no_results_widget_new (void)
{
GtkWidget *widget;
widget = padded_label_new (_("No languages found"));
gtk_widget_set_sensitive (widget, FALSE);
return widget;
}
static void
add_one_language (CcLanguageChooser *chooser,
const char *locale_id,
gboolean is_initial)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
GtkWidget *widget;
if (!cc_common_language_has_font (locale_id)) {
return;
}
widget = language_widget_new (locale_id, !is_initial);
if (widget)
gtk_list_box_append (GTK_LIST_BOX (priv->language_list), widget);
}
static void
add_languages (CcLanguageChooser *chooser,
char **locale_ids,
GHashTable *initial)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
GHashTableIter iter;
gchar *key;
g_hash_table_iter_init (&iter, initial);
while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) {
add_one_language (chooser, key, TRUE);
}
while (*locale_ids) {
const gchar *locale_id;
locale_id = *locale_ids;
locale_ids ++;
if (!g_hash_table_lookup (initial, locale_id))
add_one_language (chooser, locale_id, FALSE);
}
gtk_list_box_append (GTK_LIST_BOX (priv->language_list), priv->more_item);
gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), priv->no_results);
}
static void
add_all_languages (CcLanguageChooser *chooser)
{
g_auto(GStrv) locale_ids = NULL;
g_autoptr(GHashTable) initial = NULL;
locale_ids = gnome_get_all_locales ();
initial = cc_common_language_get_initial_languages ();
add_languages (chooser, locale_ids, initial);
}
static gboolean
language_visible (GtkListBoxRow *row,
gpointer user_data)
{
CcLanguageChooser *chooser = user_data;
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
LanguageWidget *widget;
gboolean visible;
GtkWidget *child;
const char *search_term;
child = gtk_list_box_row_get_child (row);
if (child == priv->more_item)
return !priv->showing_extra;
widget = get_language_widget (child);
if (!priv->showing_extra && widget->is_extra)
return FALSE;
search_term = gtk_editable_get_text (GTK_EDITABLE (priv->filter_entry));
if (!search_term || !*search_term)
return TRUE;
visible = FALSE;
visible = g_str_match_string (search_term, widget->locale_name, TRUE);
if (visible)
goto out;
visible = g_str_match_string (search_term, widget->locale_current_name, TRUE);
if (visible)
goto out;
visible = g_str_match_string (search_term, widget->locale_untranslated_name, TRUE);
if (visible)
goto out;
out:
return visible;
}
static gint
sort_languages (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer data)
{
LanguageWidget *la, *lb;
int ret;
la = get_language_widget (gtk_list_box_row_get_child (a));
lb = get_language_widget (gtk_list_box_row_get_child (b));
if (la == NULL)
return 1;
if (lb == NULL)
return -1;
if (la->is_extra && !lb->is_extra)
return 1;
if (!la->is_extra && lb->is_extra)
return -1;
ret = g_strcmp0 (la->sort_key, lb->sort_key);
if (ret != 0)
return ret;
return g_strcmp0 (la->locale_id, lb->locale_id);
}
static void
filter_changed (GtkEntry *entry,
CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
}
static void
show_more (CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
gtk_widget_grab_focus (priv->filter_entry);
gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL);
priv->showing_extra = TRUE;
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]);
}
static void
set_locale_id (CcLanguageChooser *chooser,
const gchar *new_locale_id)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
if (g_strcmp0 (priv->language, new_locale_id) == 0)
return;
g_free (priv->language);
priv->language = g_strdup (new_locale_id);
sync_all_checkmarks (chooser);
g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_LANGUAGE]);
}
static gboolean
confirm_choice (gpointer data)
{
GtkWidget *widget = data;
g_signal_emit (widget, signals[CONFIRM], 0);
return G_SOURCE_REMOVE;
}
static void
row_activated (GtkListBox *box,
GtkListBoxRow *row,
CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
GtkWidget *child;
LanguageWidget *widget;
if (row == NULL)
return;
child = gtk_list_box_row_get_child (row);
if (child == priv->more_item) {
show_more (chooser);
} else {
widget = get_language_widget (child);
if (widget == NULL)
return;
if (g_strcmp0 (priv->language, widget->locale_id) == 0)
g_idle_add (confirm_choice, chooser);
else
set_locale_id (chooser, widget->locale_id);
}
}
static void
cc_language_chooser_constructed (GObject *object)
{
CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
G_OBJECT_CLASS (cc_language_chooser_parent_class)->constructed (object);
priv->more_item = more_widget_new ();
priv->no_results = no_results_widget_new ();
gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->language_list),
sort_languages, chooser, NULL);
gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->language_list),
language_visible, chooser, NULL);
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->language_list),
GTK_SELECTION_NONE);
add_all_languages (chooser);
g_signal_connect (priv->filter_entry, "changed",
G_CALLBACK (filter_changed),
chooser);
g_signal_connect (priv->language_list, "row-activated",
G_CALLBACK (row_activated), chooser);
if (priv->language == NULL)
priv->language = cc_common_language_get_current_language ();
sync_all_checkmarks (chooser);
}
static void
cc_language_chooser_finalize (GObject *object)
{
CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
g_free (priv->language);
G_OBJECT_CLASS (cc_language_chooser_parent_class)->finalize (object);
}
static void
cc_language_chooser_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
switch (prop_id) {
case PROP_LANGUAGE:
g_value_set_string (value, cc_language_chooser_get_language (chooser));
break;
case PROP_SHOWING_EXTRA:
g_value_set_boolean (value, cc_language_chooser_get_showing_extra (chooser));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
cc_language_chooser_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
switch (prop_id) {
case PROP_LANGUAGE:
cc_language_chooser_set_language (chooser, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
cc_language_chooser_class_init (CcLanguageChooserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/control-center/language-chooser.ui");
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, filter_entry);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, language_list);
object_class->finalize = cc_language_chooser_finalize;
object_class->get_property = cc_language_chooser_get_property;
object_class->set_property = cc_language_chooser_set_property;
object_class->constructed = cc_language_chooser_constructed;
signals[CONFIRM] = g_signal_new ("confirm",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CcLanguageChooserClass, confirm),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
obj_props[PROP_LANGUAGE] =
g_param_spec_string ("language", "", "", "",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
obj_props[PROP_SHOWING_EXTRA] =
g_param_spec_string ("showing-extra", "", "", "",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
}
static void
cc_language_chooser_init (CcLanguageChooser *chooser)
{
gtk_widget_init_template (GTK_WIDGET (chooser));
}
void
cc_language_chooser_clear_filter (CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
gtk_editable_set_text (GTK_EDITABLE (priv->filter_entry), "");
}
const gchar *
cc_language_chooser_get_language (CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
return priv->language;
}
void
cc_language_chooser_set_language (CcLanguageChooser *chooser,
const gchar *language)
{
set_locale_id (chooser, language);
}
gboolean
cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser)
{
CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
return priv->showing_extra;
}

View file

@ -0,0 +1,63 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
* Matthias Clasen <mclasen@redhat.com>
*/
#ifndef __CC_LANGUAGE_CHOOSER_H__
#define __CC_LANGUAGE_CHOOSER_H__
#include <gtk/gtk.h>
#include <glib-object.h>
#define CC_TYPE_LANGUAGE_CHOOSER (cc_language_chooser_get_type ())
#define CC_LANGUAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooser))
#define CC_LANGUAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooserClass))
#define CC_IS_LANGUAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_LANGUAGE_CHOOSER))
#define CC_IS_LANGUAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_LANGUAGE_CHOOSER))
#define CC_LANGUAGE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_LANGUAGE_CHOOSER, CcLanguageChooserClass))
G_BEGIN_DECLS
typedef struct _CcLanguageChooser CcLanguageChooser;
typedef struct _CcLanguageChooserClass CcLanguageChooserClass;
struct _CcLanguageChooser
{
GtkBox parent;
};
struct _CcLanguageChooserClass
{
GtkBoxClass parent_class;
void (*confirm) (CcLanguageChooser *chooser);
};
GType cc_language_chooser_get_type (void);
void cc_language_chooser_clear_filter (CcLanguageChooser *chooser);
const gchar * cc_language_chooser_get_language (CcLanguageChooser *chooser);
void cc_language_chooser_set_language (CcLanguageChooser *chooser,
const gchar *language);
gboolean cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser);
G_END_DECLS
#endif /* __CC_LANGUAGE_CHOOSER_H__ */

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
*
* The Control Center 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 2 of the License, or (at your
* option) any later version.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "config.h"
#include <string.h>
#include "cc-util.h"
/* Combining diacritical mark?
* Basic range: [0x0300,0x036F]
* Supplement: [0x1DC0,0x1DFF]
* For Symbols: [0x20D0,0x20FF]
* Half marks: [0xFE20,0xFE2F]
*/
#define IS_CDM_UCS4(c) (((c) >= 0x0300 && (c) <= 0x036F) || \
((c) >= 0x1DC0 && (c) <= 0x1DFF) || \
((c) >= 0x20D0 && (c) <= 0x20FF) || \
((c) >= 0xFE20 && (c) <= 0xFE2F))
/* Copied from tracker/src/libtracker-fts/tracker-parser-glib.c under the GPL
* And then from gnome-shell/src/shell-util.c
*
* Originally written by Aleksander Morgado <aleksander@gnu.org>
*/
char *
cc_util_normalize_casefold_and_unaccent (const char *str)
{
char *normalized, *tmp;
int i = 0, j = 0, ilen;
if (str == NULL)
return NULL;
normalized = g_utf8_normalize (str, -1, G_NORMALIZE_NFKD);
tmp = g_utf8_casefold (normalized, -1);
g_free (normalized);
ilen = strlen (tmp);
while (i < ilen)
{
gunichar unichar;
gchar *next_utf8;
gint utf8_len;
/* Get next character of the word as UCS4 */
unichar = g_utf8_get_char_validated (&tmp[i], -1);
/* Invalid UTF-8 character or end of original string. */
if (unichar == (gunichar) -1 ||
unichar == (gunichar) -2)
{
break;
}
/* Find next UTF-8 character */
next_utf8 = g_utf8_next_char (&tmp[i]);
utf8_len = next_utf8 - &tmp[i];
if (IS_CDM_UCS4 ((guint32) unichar))
{
/* If the given unichar is a combining diacritical mark,
* just update the original index, not the output one */
i += utf8_len;
continue;
}
/* If already found a previous combining
* diacritical mark, indexes are different so
* need to copy characters. As output and input
* buffers may overlap, need to use memmove
* instead of memcpy */
if (i != j)
{
memmove (&tmp[j], &tmp[i], utf8_len);
}
/* Update both indexes */
i += utf8_len;
j += utf8_len;
}
/* Force proper string end */
tmp[j] = '\0';
return tmp;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
*
* The Control Center 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 2 of the License, or (at your
* option) any later version.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _CC_UTIL_H
#define _CC_UTIL_H
#include <glib.h>
char *cc_util_normalize_casefold_and_unaccent (const char *str);
#endif

View file

@ -0,0 +1,310 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
* Michael Wood <michael.g.wood@intel.com>
*
* Based on gnome-control-center cc-region-panel.c
*/
/* Language page {{{1 */
#define PAGE_ID "language"
#define GNOME_SYSTEM_LOCALE_DIR "org.gnome.system.locale"
#define REGION_KEY "region"
#include "config.h"
#include "language-resources.h"
#include "gis-welcome-widget.h"
#include "cc-language-chooser.h"
#include "gis-language-page.h"
#include <act/act-user-manager.h>
#include <polkit/polkit.h>
#include <locale.h>
#include <gtk/gtk.h>
struct _GisLanguagePagePrivate
{
GtkWidget *logo;
GtkWidget *welcome_widget;
GtkWidget *language_chooser;
GDBusProxy *localed;
GPermission *permission;
const gchar *new_locale_id;
GCancellable *cancellable;
};
typedef struct _GisLanguagePagePrivate GisLanguagePagePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GisLanguagePage, gis_language_page, GIS_TYPE_PAGE);
static void
set_localed_locale (GisLanguagePage *self)
{
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (self);
GVariantBuilder *b;
gchar *s;
b = g_variant_builder_new (G_VARIANT_TYPE ("as"));
s = g_strconcat ("LANG=", priv->new_locale_id, NULL);
g_variant_builder_add (b, "s", s);
g_free (s);
g_dbus_proxy_call (priv->localed,
"SetLocale",
g_variant_new ("(asb)", b, TRUE),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL, NULL);
g_variant_builder_unref (b);
}
static void
change_locale_permission_acquired (GObject *source,
GAsyncResult *res,
gpointer data)
{
GisLanguagePage *page = GIS_LANGUAGE_PAGE (data);
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page);
GError *error = NULL;
gboolean allowed;
allowed = g_permission_acquire_finish (priv->permission, res, &error);
if (error) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to acquire permission: %s", error->message);
g_error_free (error);
return;
}
if (allowed)
set_localed_locale (page);
}
static void
user_loaded (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
gchar *new_locale_id = user_data;
act_user_set_language (ACT_USER (object), new_locale_id);
g_free (new_locale_id);
}
static void
language_changed (CcLanguageChooser *chooser,
GParamSpec *pspec,
GisLanguagePage *page)
{
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page);
GisDriver *driver;
GSettings *region_settings;
ActUser *user;
priv->new_locale_id = cc_language_chooser_get_language (chooser);
driver = GIS_PAGE (page)->driver;
gis_driver_set_user_language (driver, priv->new_locale_id, TRUE);
if (gis_driver_get_mode (driver) == GIS_DRIVER_MODE_NEW_USER) {
if (g_permission_get_allowed (priv->permission)) {
set_localed_locale (page);
}
else if (g_permission_get_can_acquire (priv->permission)) {
g_permission_acquire_async (priv->permission,
NULL,
change_locale_permission_acquired,
page);
}
}
/* Ensure we won't override the selected language for format strings */
region_settings = g_settings_new (GNOME_SYSTEM_LOCALE_DIR);
g_settings_reset (region_settings, REGION_KEY);
g_object_unref (region_settings);
user = act_user_manager_get_user (act_user_manager_get_default (),
g_get_user_name ());
if (act_user_is_loaded (user))
act_user_set_language (user, priv->new_locale_id);
else
g_signal_connect (user,
"notify::is-loaded",
G_CALLBACK (user_loaded),
g_strdup (priv->new_locale_id));
gis_welcome_widget_show_locale (GIS_WELCOME_WIDGET (priv->welcome_widget),
priv->new_locale_id);
}
static void
localed_proxy_ready (GObject *source,
GAsyncResult *res,
gpointer data)
{
GisLanguagePage *self = data;
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (self);
GDBusProxy *proxy;
GError *error = NULL;
proxy = g_dbus_proxy_new_finish (res, &error);
if (!proxy) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to contact localed: %s", error->message);
g_error_free (error);
return;
}
priv->localed = proxy;
}
static void
update_distro_logo (GisLanguagePage *page)
{
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page);
g_autofree char *id = g_get_os_info (G_OS_INFO_KEY_ID);
gsize i;
static const struct {
const char *id;
const char *logo;
} id_to_logo[] = {
{ "debian", "emblem-debian" },
{ "fedora", "fedora-logo-icon" },
{ "ubuntu", "ubuntu-logo-icon" },
{ "openSUSE Tumbleweed", "opensuse-logo-icon" },
{ "openSUSE Leap", "opensuse-logo-icon" },
{ "SLED", "suse-logo-icon" },
{ "SLES", "suse-logo-icon" },
};
for (i = 0; i < G_N_ELEMENTS (id_to_logo); i++)
{
if (g_strcmp0 (id, id_to_logo[i].id) == 0)
{
g_object_set (priv->logo, "icon-name", id_to_logo[i].logo, NULL);
break;
}
}
}
static void
language_confirmed (CcLanguageChooser *chooser,
GisLanguagePage *page)
{
gis_assistant_next_page (gis_driver_get_assistant (GIS_PAGE (page)->driver));
}
static void
gis_language_page_constructed (GObject *object)
{
GisLanguagePage *page = GIS_LANGUAGE_PAGE (object);
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page);
GDBusConnection *bus;
g_type_ensure (CC_TYPE_LANGUAGE_CHOOSER);
G_OBJECT_CLASS (gis_language_page_parent_class)->constructed (object);
update_distro_logo (page);
g_signal_connect (priv->language_chooser, "notify::language",
G_CALLBACK (language_changed), page);
g_signal_connect (priv->language_chooser, "confirm",
G_CALLBACK (language_confirmed), page);
/* If we're in new user mode then we're manipulating system settings */
if (gis_driver_get_mode (GIS_PAGE (page)->driver) == GIS_DRIVER_MODE_NEW_USER)
{
priv->permission = polkit_permission_new_sync ("org.freedesktop.locale1.set-locale", NULL, NULL, NULL);
bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
g_dbus_proxy_new (bus,
G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES,
NULL,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.locale1",
priv->cancellable,
(GAsyncReadyCallback) localed_proxy_ready,
object);
g_object_unref (bus);
}
gis_page_set_complete (GIS_PAGE (page), TRUE);
gtk_widget_set_visible (GTK_WIDGET (page), TRUE);
}
static void
gis_language_page_locale_changed (GisPage *page)
{
gis_page_set_title (GIS_PAGE (page), _("Welcome"));
}
static void
gis_language_page_dispose (GObject *object)
{
GisLanguagePage *page = GIS_LANGUAGE_PAGE (object);
GisLanguagePagePrivate *priv = gis_language_page_get_instance_private (page);
g_clear_object (&priv->permission);
g_clear_object (&priv->localed);
g_clear_object (&priv->cancellable);
G_OBJECT_CLASS (gis_language_page_parent_class)->dispose (object);
}
static void
gis_language_page_class_init (GisLanguagePageClass *klass)
{
GisPageClass *page_class = GIS_PAGE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-language-page.ui");
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, welcome_widget);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, language_chooser);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisLanguagePage, logo);
page_class->page_id = PAGE_ID;
page_class->locale_changed = gis_language_page_locale_changed;
object_class->constructed = gis_language_page_constructed;
object_class->dispose = gis_language_page_dispose;
}
static void
gis_language_page_init (GisLanguagePage *page)
{
g_type_ensure (GIS_TYPE_WELCOME_WIDGET);
g_type_ensure (CC_TYPE_LANGUAGE_CHOOSER);
gtk_widget_init_template (GTK_WIDGET (page));
}
GisPage *
gis_prepare_language_page (GisDriver *driver)
{
return g_object_new (GIS_TYPE_LANGUAGE_PAGE,
"driver", driver,
NULL);
}

View file

@ -0,0 +1,57 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GIS_LANGUAGE_PAGE_H__
#define __GIS_LANGUAGE_PAGE_H__
#include <glib-object.h>
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
#define GIS_TYPE_LANGUAGE_PAGE (gis_language_page_get_type ())
#define GIS_LANGUAGE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePage))
#define GIS_LANGUAGE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePageClass))
#define GIS_IS_LANGUAGE_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_LANGUAGE_PAGE))
#define GIS_IS_LANGUAGE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_LANGUAGE_PAGE))
#define GIS_LANGUAGE_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_LANGUAGE_PAGE, GisLanguagePageClass))
typedef struct _GisLanguagePage GisLanguagePage;
typedef struct _GisLanguagePageClass GisLanguagePageClass;
struct _GisLanguagePage
{
GisPage parent;
};
struct _GisLanguagePageClass
{
GisPageClass parent_class;
};
GType gis_language_page_get_type (void);
GisPage *gis_prepare_language_page (GisDriver *driver);
G_END_DECLS
#endif /* __GIS_LANGUAGE_PAGE_H__ */

View file

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<interface>
<template class="GisLanguagePage" parent="GisPage">
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkBox" id="box">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="hexpand" bind-source="language_chooser" bind-property="visible" bind-flags="invert-boolean|sync-create"/>
<property name="vexpand" bind-source="language_chooser" bind-property="visible" bind-flags="invert-boolean|sync-create"/>
<child>
<object class="GtkImage" id="logo">
<property name="visible" bind-source="GisLanguagePage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/>
<property name="margin_top">24</property>
<property name="pixel_size">96</property>
<property name="icon_name">start-here-symbolic</property>
</object>
</child>
<child>
<object class="GisWelcomeWidget" id="welcome_widget">
<property name="margin_top">18</property>
<property name="margin_bottom">40</property>
</object>
</child>
</object>
</child>
<child>
<object class="CcLanguageChooser" id="language_chooser">
<property name="margin_bottom">18</property>
<property name="width_request">400</property>
<property name="valign">start</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,242 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#include "config.h"
#include "gis-welcome-widget.h"
#include <errno.h>
#include <locale.h>
#include <glib/gi18n.h>
#include "cc-common-language.h"
struct _GisWelcomeWidgetPrivate
{
AdwCarousel *carousel;
GHashTable *translation_widgets; /* (element-type owned utf8 unowned GtkWidget) (owned) */
guint timeout_id;
};
typedef struct _GisWelcomeWidgetPrivate GisWelcomeWidgetPrivate;
#define TIMEOUT 5
G_DEFINE_TYPE_WITH_PRIVATE (GisWelcomeWidget, gis_welcome_widget, ADW_TYPE_BIN);
static gboolean
advance_stack (gpointer user_data)
{
GisWelcomeWidget *widget = user_data;
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
GtkWidget *child;
unsigned int next_page;
unsigned int n_pages;
double current_page;
n_pages = adw_carousel_get_n_pages (priv->carousel);
if (n_pages == 0)
goto out;
current_page = ceil (adw_carousel_get_position (priv->carousel));
next_page = ((int) current_page + 1) % n_pages;
child = gtk_widget_get_first_child (GTK_WIDGET (priv->carousel));
while (next_page-- > 0)
child = gtk_widget_get_next_sibling (child);
adw_carousel_scroll_to (priv->carousel, child, TRUE);
out:
return G_SOURCE_CONTINUE;
}
static void
gis_welcome_widget_start (GisWelcomeWidget *widget)
{
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
if (priv->timeout_id > 0)
return;
priv->timeout_id = g_timeout_add_seconds (5, advance_stack, widget);
}
static void
gis_welcome_widget_stop (GisWelcomeWidget *widget)
{
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
if (priv->timeout_id == 0)
return;
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
static void
gis_welcome_widget_map (GtkWidget *widget)
{
GTK_WIDGET_CLASS (gis_welcome_widget_parent_class)->map (widget);
gis_welcome_widget_start (GIS_WELCOME_WIDGET (widget));
}
static void
gis_welcome_widget_unmap (GtkWidget *widget)
{
GTK_WIDGET_CLASS (gis_welcome_widget_parent_class)->unmap (widget);
gis_welcome_widget_stop (GIS_WELCOME_WIDGET (widget));
}
static const char *
welcome (const char *locale_id)
{
locale_t locale;
locale_t old_locale;
const char *welcome;
locale = newlocale (LC_MESSAGES_MASK, locale_id, (locale_t) 0);
if (locale == (locale_t) 0)
{
if (errno == ENOENT)
g_debug ("Failed to create locale %s: %s", locale_id, g_strerror (errno));
else
g_warning ("Failed to create locale %s: %s", locale_id, g_strerror (errno));
return "Welcome!";
}
old_locale = uselocale (locale);
/* Translators: This is meant to be a warm, engaging welcome message,
* like greeting somebody at the door. If the exclamation mark is not
* suitable for this in your language you may replace it.
*/
welcome = _("Welcome!");
uselocale (old_locale);
freelocale (locale);
return welcome;
}
static GtkWidget *
big_label (const char *text)
{
GtkWidget *label = gtk_label_new (text);
gtk_widget_add_css_class (label, "title-1");
return label;
}
static void
fill_carousel (GisWelcomeWidget *widget)
{
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
g_autoptr(GHashTable) initial = cc_common_language_get_initial_languages ();
GHashTableIter iter;
gpointer key, value;
g_autoptr(GHashTable) added_translations = NULL;
added_translations = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_iter_init (&iter, initial);
while (g_hash_table_iter_next (&iter, &key, &value))
{
const char *locale_id = key;
const char *text;
GtkWidget *label;
if (!cc_common_language_has_font (locale_id))
continue;
text = welcome (locale_id);
label = g_hash_table_lookup (added_translations, text);
if (label == NULL) {
label = big_label (text);
adw_carousel_append (priv->carousel, label);
g_hash_table_insert (added_translations, (gpointer) text, label);
}
g_hash_table_insert (priv->translation_widgets, g_strdup (locale_id), label);
}
}
static void
gis_welcome_widget_constructed (GObject *object)
{
G_OBJECT_CLASS (gis_welcome_widget_parent_class)->constructed (object);
fill_carousel (GIS_WELCOME_WIDGET (object));
}
static void
gis_welcome_widget_dispose (GObject *object)
{
GisWelcomeWidget *widget = GIS_WELCOME_WIDGET (object);
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
g_clear_pointer (&priv->translation_widgets, g_hash_table_unref);
G_OBJECT_CLASS (gis_welcome_widget_parent_class)->dispose (object);
}
static void
gis_welcome_widget_class_init (GisWelcomeWidgetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/initial-setup/gis-welcome-widget.ui");
gtk_widget_class_bind_template_child_private (widget_class, GisWelcomeWidget, carousel);
object_class->constructed = gis_welcome_widget_constructed;
object_class->dispose = gis_welcome_widget_dispose;
widget_class->map = gis_welcome_widget_map;
widget_class->unmap = gis_welcome_widget_unmap;
}
static void
gis_welcome_widget_init (GisWelcomeWidget *widget)
{
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
priv->translation_widgets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
gtk_widget_init_template (GTK_WIDGET (widget));
}
void
gis_welcome_widget_show_locale (GisWelcomeWidget *widget,
const char *locale_id)
{
GisWelcomeWidgetPrivate *priv = gis_welcome_widget_get_instance_private (widget);
GtkWidget *label;
/* Restart the widget to reset the timer. */
gis_welcome_widget_stop (widget);
gis_welcome_widget_start (widget);
label = g_hash_table_lookup (priv->translation_widgets, locale_id);
if (label)
adw_carousel_scroll_to (priv->carousel, label, FALSE);
}

View file

@ -0,0 +1,56 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2013 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GIS_WELCOME_WIDGET_H__
#define __GIS_WELCOME_WIDGET_H__
#include <adwaita.h>
G_BEGIN_DECLS
#define GIS_TYPE_WELCOME_WIDGET (gis_welcome_widget_get_type ())
#define GIS_WELCOME_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidget))
#define GIS_WELCOME_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidgetClass))
#define GIS_IS_WELCOME_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_WELCOME_WIDGET))
#define GIS_IS_WELCOME_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_WELCOME_WIDGET))
#define GIS_WELCOME_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_WELCOME_WIDGET, GisWelcomeWidgetClass))
typedef struct _GisWelcomeWidget GisWelcomeWidget;
typedef struct _GisWelcomeWidgetClass GisWelcomeWidgetClass;
struct _GisWelcomeWidget
{
AdwBin parent;
};
struct _GisWelcomeWidgetClass
{
AdwBinClass parent_class;
};
GType gis_welcome_widget_get_type (void);
void gis_welcome_widget_show_locale (GisWelcomeWidget *widget,
const char *locale_id);
G_END_DECLS
#endif /* __GIS_WELCOME_WIDGET_H__ */

View file

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="3.0"/>
<template class="GisWelcomeWidget" parent="AdwBin">
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<child>
<object class="AdwCarousel" id="carousel">
<property name="halign">center</property>
<property name="interactive">False</property>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<interface>
<template class="CcLanguageChooser" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkSearchEntry" id="filter_entry">
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkListBox" id="language_list">
<property name="vexpand">True</property>
<property name="halign">fill</property>
<property name="valign">start</property>
<style>
<class name="boxed-list" />
</style>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/initial-setup">
<file preprocess="xml-stripblanks" alias="gis-language-page.ui">gis-language-page.ui</file>
<file preprocess="xml-stripblanks" alias="gis-welcome-widget.ui">gis-welcome-widget.ui</file>
</gresource>
<gresource prefix="/org/gnome/control-center">
<file preprocess="xml-stripblanks" alias="language-chooser.ui">language-chooser.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,16 @@
sources += gnome.compile_resources(
'language-resources',
files('language.gresource.xml'),
c_name: 'language'
)
sources += files(
'cc-language-chooser.c',
'cc-language-chooser.h',
'cc-util.c',
'cc-util.h',
'gis-welcome-widget.c',
'gis-welcome-widget.h',
'gis-language-page.c',
'gis-language-page.h',
)

View file

@ -0,0 +1,20 @@
pages = [
'account',
'language',
'keyboard',
'network',
'timezone',
'privacy',
'password',
'software',
'summary',
'welcome',
]
if libmalcontent_dep.found() and libmalcontent_ui_dep.found()
pages += 'parental-controls'
endif
foreach page: pages
subdir (page)
endforeach

View file

@ -0,0 +1,827 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
/* Network page {{{1 */
#define PAGE_ID "network"
#include "config.h"
#include "network-resources.h"
#include "gis-network-page.h"
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "network-dialogs.h"
#include "gis-page-header.h"
typedef enum {
NM_AP_SEC_UNKNOWN,
NM_AP_SEC_NONE,
NM_AP_SEC_WEP,
NM_AP_SEC_WPA,
NM_AP_SEC_WPA2
} NMAccessPointSecurity;
struct _GisNetworkPagePrivate {
GtkWidget *network_list;
GtkWidget *no_network_label;
GtkWidget *no_network_spinner;
GtkWidget *turn_on_label;
GtkWidget *turn_on_switch;
NMClient *nm_client;
NMDevice *nm_device;
gboolean refreshing;
GtkSizeGroup *icons;
guint refresh_timeout_id;
/* TRUE if the page has ever been shown to the user. */
gboolean ever_shown;
};
typedef struct _GisNetworkPagePrivate GisNetworkPagePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GisNetworkPage, gis_network_page, GIS_TYPE_PAGE);
static GPtrArray *
get_strongest_unique_aps (const GPtrArray *aps)
{
GBytes *ssid;
GBytes *ssid_tmp;
GPtrArray *unique = NULL;
gboolean add_ap;
guint i;
guint j;
NMAccessPoint *ap;
NMAccessPoint *ap_tmp;
/* we will have multiple entries for typical hotspots,
* just keep the one with the strongest signal
*/
unique = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
if (aps == NULL)
goto out;
for (i = 0; i < aps->len; i++) {
ap = NM_ACCESS_POINT (g_ptr_array_index (aps, i));
ssid = nm_access_point_get_ssid (ap);
add_ap = TRUE;
if (!ssid)
continue;
/* get already added list */
for (j = 0; j < unique->len; j++) {
ap_tmp = NM_ACCESS_POINT (g_ptr_array_index (unique, j));
ssid_tmp = nm_access_point_get_ssid (ap_tmp);
/* is this the same type and data? */
if (ssid_tmp &&
nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid),
g_bytes_get_data (ssid_tmp, NULL), g_bytes_get_size (ssid_tmp), TRUE)) {
/* the new access point is stronger */
if (nm_access_point_get_strength (ap) >
nm_access_point_get_strength (ap_tmp)) {
g_ptr_array_remove (unique, ap_tmp);
add_ap = TRUE;
} else {
add_ap = FALSE;
}
break;
}
}
if (add_ap) {
g_ptr_array_add (unique, g_object_ref (ap));
}
}
out:
return unique;
}
static guint
get_access_point_security (NMAccessPoint *ap)
{
NM80211ApFlags flags;
NM80211ApSecurityFlags wpa_flags;
NM80211ApSecurityFlags rsn_flags;
guint type;
flags = nm_access_point_get_flags (ap);
wpa_flags = nm_access_point_get_wpa_flags (ap);
rsn_flags = nm_access_point_get_rsn_flags (ap);
if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) &&
wpa_flags == NM_802_11_AP_SEC_NONE &&
rsn_flags == NM_802_11_AP_SEC_NONE)
type = NM_AP_SEC_NONE;
else if ((flags & NM_802_11_AP_FLAGS_PRIVACY) &&
wpa_flags == NM_802_11_AP_SEC_NONE &&
rsn_flags == NM_802_11_AP_SEC_NONE)
type = NM_AP_SEC_WEP;
else if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) &&
wpa_flags != NM_802_11_AP_SEC_NONE &&
rsn_flags != NM_802_11_AP_SEC_NONE)
type = NM_AP_SEC_WPA;
else
type = NM_AP_SEC_WPA2;
return type;
}
static gint
ap_sort (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer data)
{
GtkWidget *wa, *wb;
guint sa, sb;
wa = gtk_list_box_row_get_child (a);
wb = gtk_list_box_row_get_child (b);
sa = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wa), "strength"));
sb = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (wb), "strength"));
if (sa > sb) return -1;
if (sb > sa) return 1;
return 0;
}
static void
add_access_point (GisNetworkPage *page, NMAccessPoint *ap, NMAccessPoint *active)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
GBytes *ssid;
GBytes *ssid_active = NULL;
gchar *ssid_text;
const gchar *object_path;
gboolean activated, activating;
guint security;
guint strength;
const gchar *icon_name;
GtkWidget *row;
GtkWidget *widget;
GtkWidget *grid;
GtkWidget *state_widget = NULL;
ssid = nm_access_point_get_ssid (ap);
object_path = nm_object_get_path (NM_OBJECT (ap));
if (ssid == NULL)
return;
ssid_text = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
if (active)
ssid_active = nm_access_point_get_ssid (active);
if (ssid_active && nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid),
g_bytes_get_data (ssid_active, NULL), g_bytes_get_size (ssid_active), TRUE)) {
switch (nm_device_get_state (priv->nm_device))
{
case NM_DEVICE_STATE_PREPARE:
case NM_DEVICE_STATE_CONFIG:
case NM_DEVICE_STATE_NEED_AUTH:
case NM_DEVICE_STATE_IP_CONFIG:
case NM_DEVICE_STATE_SECONDARIES:
activated = FALSE;
activating = TRUE;
break;
case NM_DEVICE_STATE_ACTIVATED:
activated = TRUE;
activating = FALSE;
break;
default:
activated = FALSE;
activating = FALSE;
break;
}
} else {
activated = FALSE;
activating = FALSE;
}
security = get_access_point_security (ap);
strength = nm_access_point_get_strength (ap);
row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_start (row, 12);
gtk_widget_set_margin_end (row, 12);
widget = gtk_label_new (ssid_text);
gtk_widget_set_margin_top (widget, 12);
gtk_widget_set_margin_bottom (widget, 12);
gtk_box_append (GTK_BOX (row), widget);
if (activated) {
state_widget = gtk_image_new_from_icon_name ("object-select-symbolic");
} else if (activating) {
state_widget = gtk_spinner_new ();
gtk_spinner_start (GTK_SPINNER (state_widget));
}
if (state_widget) {
gtk_widget_set_halign (state_widget, GTK_ALIGN_START);
gtk_widget_set_valign (state_widget, GTK_ALIGN_CENTER);
gtk_widget_set_hexpand (state_widget, TRUE);
gtk_box_append (GTK_BOX (row), state_widget);
}
grid = gtk_grid_new ();
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
gtk_widget_set_valign (grid, GTK_ALIGN_CENTER);
gtk_size_group_add_widget (priv->icons, grid);
gtk_box_append (GTK_BOX (row), grid);
if (security != NM_AP_SEC_UNKNOWN &&
security != NM_AP_SEC_NONE) {
widget = gtk_image_new_from_icon_name ("network-wireless-encrypted-symbolic");
gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1);
}
if (strength < 20)
icon_name = "network-wireless-signal-none-symbolic";
else if (strength < 40)
icon_name = "network-wireless-signal-weak-symbolic";
else if (strength < 50)
icon_name = "network-wireless-signal-ok-symbolic";
else if (strength < 80)
icon_name = "network-wireless-signal-good-symbolic";
else
icon_name = "network-wireless-signal-excellent-symbolic";
widget = gtk_image_new_from_icon_name (icon_name);
gtk_widget_set_halign (widget, GTK_ALIGN_END);
gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);
/* if this connection is the active one or is being activated, then make sure
* it's sorted before all others */
if (activating || activated)
strength = G_MAXUINT;
g_object_set_data (G_OBJECT (row), "object-path", (gpointer) object_path);
g_object_set_data (G_OBJECT (row), "ssid", (gpointer) ssid);
g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (strength));
gtk_list_box_append (GTK_LIST_BOX (priv->network_list), row);
}
static void
add_access_point_other (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
GtkWidget *row;
GtkWidget *widget;
row = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_start (row, 12);
gtk_widget_set_margin_end (row, 12);
widget = gtk_label_new (C_("Wireless access point", "Other…"));
gtk_widget_set_margin_top (widget, 12);
gtk_widget_set_margin_bottom (widget, 12);
gtk_box_append (GTK_BOX (row), widget);
g_object_set_data (G_OBJECT (row), "object-path", "ap-other...");
g_object_set_data (G_OBJECT (row), "strength", GUINT_TO_POINTER (0));
gtk_list_box_append (GTK_LIST_BOX (priv->network_list), row);
}
static gboolean refresh_wireless_list (GisNetworkPage *page);
static void
cancel_periodic_refresh (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
if (priv->refresh_timeout_id == 0)
return;
g_debug ("Stopping periodic/scheduled Wi-Fi list refresh");
g_clear_handle_id (&priv->refresh_timeout_id, g_source_remove);
}
static gboolean
refresh_again (gpointer user_data)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (user_data);
refresh_wireless_list (page);
return G_SOURCE_REMOVE;
}
static void
start_periodic_refresh (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
static const guint periodic_wifi_refresh_timeout_sec = 10;
cancel_periodic_refresh (page);
g_debug ("Starting periodic Wi-Fi list refresh (every %u secs)",
periodic_wifi_refresh_timeout_sec);
priv->refresh_timeout_id = g_timeout_add_seconds (periodic_wifi_refresh_timeout_sec,
refresh_again, page);
}
static gboolean
refresh_wireless_list (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
NMAccessPoint *active_ap = NULL;
NMAccessPoint *ap;
const GPtrArray *aps;
GPtrArray *unique_aps;
GtkWidget *child;
guint i;
gboolean enabled;
g_debug ("Refreshing Wi-Fi networks list");
priv->refreshing = TRUE;
g_assert (NM_IS_DEVICE_WIFI (priv->nm_device));
cancel_periodic_refresh (page);
active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (priv->nm_device));
while ((child = gtk_widget_get_first_child (priv->network_list)) != NULL)
gtk_list_box_remove (GTK_LIST_BOX (priv->network_list), child);
aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (priv->nm_device));
enabled = nm_client_wireless_get_enabled (priv->nm_client);
if (aps == NULL || aps->len == 0) {
gboolean hw_enabled;
hw_enabled = nm_client_wireless_hardware_get_enabled (priv->nm_client);
if (!enabled || !hw_enabled) {
gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Wireless networking is disabled"));
gtk_widget_set_visible (priv->no_network_label, TRUE);
gtk_widget_set_visible (priv->no_network_spinner, FALSE);
gtk_widget_set_visible (priv->turn_on_label, hw_enabled);
gtk_widget_set_visible (priv->turn_on_switch, hw_enabled);
} else {
gtk_label_set_text (GTK_LABEL (priv->no_network_label), _("Checking for available wireless networks"));
gtk_widget_set_visible (priv->no_network_spinner, TRUE);
gtk_widget_set_visible (priv->no_network_label, TRUE);
gtk_widget_set_visible (priv->turn_on_label, FALSE);
gtk_widget_set_visible (priv->turn_on_switch, FALSE);
}
gtk_widget_set_visible (priv->network_list, FALSE);
goto out;
} else {
gtk_widget_set_visible (priv->no_network_spinner, FALSE);
gtk_widget_set_visible (priv->no_network_label, FALSE);
gtk_widget_set_visible (priv->turn_on_label, FALSE);
gtk_widget_set_visible (priv->turn_on_switch, FALSE);
gtk_widget_set_visible (priv->network_list, TRUE);
}
unique_aps = get_strongest_unique_aps (aps);
for (i = 0; i < unique_aps->len; i++) {
ap = NM_ACCESS_POINT (g_ptr_array_index (unique_aps, i));
add_access_point (page, ap, active_ap);
}
g_ptr_array_unref (unique_aps);
add_access_point_other (page);
out:
if (enabled)
start_periodic_refresh (page);
priv->refreshing = FALSE;
return G_SOURCE_REMOVE;
}
/* Avoid repeated calls to refreshing the wireless list by making it refresh at
* most once per second */
static void
schedule_refresh_wireless_list (GisNetworkPage *page)
{
static const guint refresh_wireless_list_timeout_sec = 1;
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
cancel_periodic_refresh (page);
g_debug ("Delaying Wi-Fi list refresh (for %u sec)",
refresh_wireless_list_timeout_sec);
priv->refresh_timeout_id = g_timeout_add_seconds (refresh_wireless_list_timeout_sec,
(GSourceFunc) refresh_wireless_list,
page);
}
static void
connection_activate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
NMClient *client = NM_CLIENT (object);
NMActiveConnection *connection = NULL;
g_autoptr(GError) error = NULL;
connection = nm_client_activate_connection_finish (client, result, &error);
if (connection != NULL) {
g_clear_object (&connection);
} else {
/* failed to activate */
g_warning ("Failed to activate a connection: %s", error->message);
}
}
static void
connection_add_activate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
NMClient *client = NM_CLIENT (object);
NMActiveConnection *connection = NULL;
g_autoptr(GError) error = NULL;
connection = nm_client_add_and_activate_connection_finish (client, result, &error);
if (connection != NULL) {
g_clear_object (&connection);
} else {
/* failed to activate */
g_warning ("Failed to add and activate a connection: %s", error->message);
}
}
static void
connect_to_hidden_network (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (page));
cc_network_panel_connect_to_hidden_network (GTK_WIDGET (root), priv->nm_client);
}
static void
row_activated (GtkListBox *box,
GtkListBoxRow *row,
GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
gchar *object_path;
const GPtrArray *list;
GPtrArray *filtered;
NMConnection *connection;
NMConnection *connection_to_activate;
NMSettingWireless *setting;
GBytes *ssid;
GBytes *ssid_target;
GtkWidget *child;
int i;
if (priv->refreshing)
return;
child = gtk_list_box_row_get_child (row);
object_path = g_object_get_data (G_OBJECT (child), "object-path");
ssid_target = g_object_get_data (G_OBJECT (child), "ssid");
if (g_strcmp0 (object_path, "ap-other...") == 0) {
connect_to_hidden_network (page);
goto out;
}
list = nm_client_get_connections (priv->nm_client);
filtered = nm_device_filter_connections (priv->nm_device, list);
connection_to_activate = NULL;
for (i = 0; i < filtered->len; i++) {
connection = NM_CONNECTION (filtered->pdata[i]);
setting = nm_connection_get_setting_wireless (connection);
if (!NM_IS_SETTING_WIRELESS (setting))
continue;
ssid = nm_setting_wireless_get_ssid (setting);
if (ssid == NULL)
continue;
if (nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid),
g_bytes_get_data (ssid_target, NULL), g_bytes_get_size (ssid_target), TRUE)) {
connection_to_activate = connection;
break;
}
}
g_ptr_array_unref (filtered);
if (connection_to_activate != NULL) {
nm_client_activate_connection_async (priv->nm_client,
connection_to_activate,
priv->nm_device, NULL,
NULL,
connection_activate_cb, page);
return;
}
nm_client_add_and_activate_connection_async (priv->nm_client,
NULL,
priv->nm_device, object_path,
NULL,
connection_add_activate_cb, page);
out:
schedule_refresh_wireless_list (page);
}
static void
connection_state_changed (NMActiveConnection *c, GParamSpec *pspec, GisNetworkPage *page)
{
schedule_refresh_wireless_list (page);
}
static void
active_connections_changed (NMClient *client, GParamSpec *pspec, GisNetworkPage *page)
{
const GPtrArray *connections;
guint i;
connections = nm_client_get_active_connections (client);
for (i = 0; connections && (i < connections->len); i++) {
NMActiveConnection *connection;
connection = g_ptr_array_index (connections, i);
if (!g_object_get_data (G_OBJECT (connection), "has-state-changed-handler")) {
g_signal_connect (connection, "notify::state",
G_CALLBACK (connection_state_changed), page);
g_object_set_data (G_OBJECT (connection), "has-state-changed-handler", GINT_TO_POINTER (1));
}
}
}
static void
sync_complete (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
gboolean has_device;
gboolean activated;
gboolean visible;
has_device = priv->nm_device != NULL;
activated = priv->nm_device != NULL
&& nm_device_get_state (priv->nm_device) == NM_DEVICE_STATE_ACTIVATED;
if (priv->ever_shown) {
visible = TRUE;
} else if (!has_device) {
g_debug ("No network device found, hiding network page");
visible = FALSE;
} else if (activated) {
g_debug ("Activated network device found, hiding network page");
visible = FALSE;
} else {
visible = TRUE;
}
gis_page_set_complete (GIS_PAGE (page), activated);
gtk_widget_set_visible (GTK_WIDGET (page), visible);
if (has_device)
schedule_refresh_wireless_list (page);
}
static void
device_state_changed (GObject *object, GParamSpec *param, GisNetworkPage *page)
{
sync_complete (page);
}
static void
find_best_device (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
const GPtrArray *devices;
guint i;
/* FIXME: deal with multiple devices and devices being removed */
if (priv->nm_device != NULL) {
g_debug ("Already showing network device %s",
nm_device_get_description (priv->nm_device));
return;
}
devices = nm_client_get_devices (priv->nm_client);
g_return_if_fail (devices != NULL);
for (i = 0; i < devices->len; i++) {
NMDevice *device = g_ptr_array_index (devices, i);
if (!nm_device_get_managed (device))
continue;
if (nm_device_get_device_type (device) == NM_DEVICE_TYPE_WIFI) {
/* FIXME deal with multiple, dynamic devices */
priv->nm_device = g_object_ref (device);
g_debug ("Showing network device %s",
nm_device_get_description (priv->nm_device));
g_signal_connect (priv->nm_device, "notify::state",
G_CALLBACK (device_state_changed), page);
g_signal_connect (priv->nm_client, "notify::active-connections",
G_CALLBACK (active_connections_changed), page);
break;
}
}
sync_complete (page);
}
static void
device_notify_managed (NMDevice *device,
GParamSpec *param,
void *user_data)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (user_data);
find_best_device (page);
}
static void
client_device_added (NMClient *client,
NMDevice *device,
void *user_data)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (user_data);
g_signal_connect_object (device,
"notify::managed",
G_CALLBACK (device_notify_managed),
G_OBJECT (page),
0);
find_best_device (page);
}
static void
client_device_removed (NMClient *client,
NMDevice *device,
void *user_data)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (user_data);
/* TODO: reset page if priv->nm_device == device */
g_signal_handlers_disconnect_by_func (device, device_notify_managed, page);
find_best_device (page);
}
static void
monitor_network_devices (GisNetworkPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
const GPtrArray *devices;
guint i;
g_assert (priv->nm_client != NULL);
devices = nm_client_get_devices (priv->nm_client);
g_return_if_fail (devices != NULL);
for (i = 0; devices != NULL && i < devices->len; i++) {
NMDevice *device = g_ptr_array_index (devices, i);
g_signal_connect_object (device,
"notify::managed",
G_CALLBACK (device_notify_managed),
G_OBJECT (page),
0);
}
g_signal_connect_object (priv->nm_client,
"device-added",
G_CALLBACK (client_device_added),
G_OBJECT (page),
0);
g_signal_connect_object (priv->nm_client,
"device-removed",
G_CALLBACK (client_device_removed),
G_OBJECT (page),
0);
find_best_device (page);
}
static void
gis_network_page_constructed (GObject *object)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (object);
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
g_autoptr(GError) error = NULL;
G_OBJECT_CLASS (gis_network_page_parent_class)->constructed (object);
gis_page_set_skippable (GIS_PAGE (page), TRUE);
priv->ever_shown = g_getenv ("GIS_ALWAYS_SHOW_NETWORK_PAGE") != NULL;
priv->icons = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->network_list), GTK_SELECTION_NONE);
gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->network_list), ap_sort, NULL, NULL);
g_signal_connect (priv->network_list, "row-activated",
G_CALLBACK (row_activated), page);
priv->nm_client = nm_client_new (NULL, &error);
if (!priv->nm_client) {
g_warning ("Can't create NetworkManager client, hiding network page: %s",
error->message);
sync_complete (page);
return;
}
g_object_bind_property (priv->nm_client, "wireless-enabled",
priv->turn_on_switch, "active",
G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
monitor_network_devices (page);
}
static void
gis_network_page_dispose (GObject *object)
{
GisNetworkPage *page = GIS_NETWORK_PAGE (object);
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (page);
g_clear_object (&priv->nm_client);
g_clear_object (&priv->nm_device);
g_clear_object (&priv->icons);
cancel_periodic_refresh (page);
G_OBJECT_CLASS (gis_network_page_parent_class)->dispose (object);
}
static void
gis_network_page_locale_changed (GisPage *page)
{
gis_page_set_title (GIS_PAGE (page), _("Network"));
}
static void
gis_network_page_shown (GisPage *page)
{
GisNetworkPagePrivate *priv = gis_network_page_get_instance_private (GIS_NETWORK_PAGE (page));
priv->ever_shown = TRUE;
}
static void
gis_network_page_class_init (GisNetworkPageClass *klass)
{
GisPageClass *page_class = GIS_PAGE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-network-page.ui");
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, network_list);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_label);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, no_network_spinner);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_label);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisNetworkPage, turn_on_switch);
page_class->page_id = PAGE_ID;
page_class->locale_changed = gis_network_page_locale_changed;
page_class->shown = gis_network_page_shown;
object_class->constructed = gis_network_page_constructed;
object_class->dispose = gis_network_page_dispose;
}
static void
gis_network_page_init (GisNetworkPage *page)
{
g_type_ensure (GIS_TYPE_PAGE_HEADER);
gtk_widget_init_template (GTK_WIDGET (page));
}
GisPage *
gis_prepare_network_page (GisDriver *driver)
{
return g_object_new (GIS_TYPE_NETWORK_PAGE,
"driver", driver,
NULL);
}

View file

@ -0,0 +1,55 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2012 Red Hat
*
* 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 2 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/>.
*
* Written by:
* Jasper St. Pierre <jstpierre@mecheye.net>
*/
#ifndef __GIS_NETWORK_PAGE_H__
#define __GIS_NETWORK_PAGE_H__
#include "gnome-initial-setup.h"
G_BEGIN_DECLS
#define GIS_TYPE_NETWORK_PAGE (gis_network_page_get_type ())
#define GIS_NETWORK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIS_TYPE_NETWORK_PAGE, GisNetworkPage))
#define GIS_NETWORK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIS_TYPE_NETWORK_PAGE, GisNetworkPageClass))
#define GIS_IS_NETWORK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIS_TYPE_NETWORK_PAGE))
#define GIS_IS_NETWORK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIS_TYPE_NETWORK_PAGE))
#define GIS_NETWORK_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIS_TYPE_NETWORK_PAGE, GisNetworkPageClass))
typedef struct _GisNetworkPage GisNetworkPage;
typedef struct _GisNetworkPageClass GisNetworkPageClass;
struct _GisNetworkPage
{
GisPage parent;
};
struct _GisNetworkPageClass
{
GisPageClass parent_class;
};
GType gis_network_page_get_type (void);
GisPage *gis_prepare_network_page (GisDriver *driver);
G_END_DECLS
#endif /* __GIS_NETWORK_PAGE_H__ */

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GisNetworkPage" parent="GisPage">
<child>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkBox" id="box">
<property name="orientation">vertical</property>
<property name="margin_bottom">32</property>
<child>
<object class="GisPageHeader" id="header">
<property name="margin_top">24</property>
<property name="title" translatable="yes">Wi-Fi</property>
<property name="subtitle" translatable="yes">Connecting to the internet helps you get new apps, information, and other upgrades. It also helps set the time and your location automatically.</property>
<property name="icon_name">network-wireless-symbolic</property>
<property name="show_icon" bind-source="GisNetworkPage" bind-property="small-screen" bind-flags="invert-boolean|sync-create"/>
</object>
</child>
<child>
<object class="GtkListBox" id="network_list">
<property name="valign">start</property>
<property name="vexpand">True</property>
<property name="margin-top">18</property>
<property name="tab-behavior">GTK_LIST_TAB_ITEM</property>
<style>
<class name="boxed-list" />
</style>
</object>
</child>
<child>
<object class="GtkGrid" id="no_network_grid">
<property name="margin_top">18</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="row-spacing">10</property>
<child>
<object class="GtkSpinner" id="no_network_spinner">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_start">6</property>
<property name="margin_end">6</property>
<property name="spinning">True</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
<property name="column-span">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="no_network_label">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="label" translatable="yes">No wireless available</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
<property name="column-span">2</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="turn_on_label">
<property name="halign">start</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Turn On</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
<property name="column-span">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkSwitch" id="turn_on_switch">
<property name="halign">end</property>
<property name="valign">center</property>
<layout>
<property name="column">2</property>
<property name="row">1</property>
<property name="column-span">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,12 @@
sources += gnome.compile_resources(
'network-resources',
files('network.gresource.xml'),
c_name: 'network'
)
sources += files(
'network-dialogs.c',
'network-dialogs.h',
'gis-network-page.c',
'gis-network-page.h'
)

Some files were not shown because too many files have changed in this diff Show more