Adding upstream version 48.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
52a58131b2
commit
4d6cf1c280
250 changed files with 93774 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
113
.gitlab-ci.yml
Normal 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
174
CONTRIBUTING.md
Normal 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
339
COPYING
Normal 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
36
DEBUGGING.md
Normal 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.
|
116
README.md
Normal file
116
README.md
Normal 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.
|
29
data/20-gnome-initial-setup.rules.in
Normal file
29
data/20-gnome-initial-setup.rules.in
Normal 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;
|
||||
});
|
14
data/dconf/defaults/00-upstream-settings
Normal file
14
data/dconf/defaults/00-upstream-settings
Normal 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
|
3
data/dconf/defaults/locks/00-upstream-settings-locks
Normal file
3
data/dconf/defaults/locks/00-upstream-settings-locks
Normal 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
|
2
data/dconf/gnome-initial-setup.in
Normal file
2
data/dconf/gnome-initial-setup.in
Normal file
|
@ -0,0 +1,2 @@
|
|||
user-db:user
|
||||
file-db:@DATADIR@/@PACKAGE@/initial-setup-dconf-defaults
|
25
data/dconf/meson.build
Normal file
25
data/dconf/meson.build
Normal 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(),
|
||||
)
|
10
data/gnome-initial-setup-copy-worker.desktop.in.in
Normal file
10
data/gnome-initial-setup-copy-worker.desktop.in.in
Normal 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@
|
18
data/gnome-initial-setup-copy-worker.service.in
Normal file
18
data/gnome-initial-setup-copy-worker.service.in
Normal 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
|
13
data/gnome-initial-setup-first-login.desktop.in.in
Normal file
13
data/gnome-initial-setup-first-login.desktop.in.in
Normal 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@
|
16
data/gnome-initial-setup-first-login.service.in
Normal file
16
data/gnome-initial-setup-first-login.service.in
Normal 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
|
3
data/gnome-initial-setup.conf
Normal file
3
data/gnome-initial-setup.conf
Normal 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
|
13
data/gnome-initial-setup.desktop.in.in
Normal file
13
data/gnome-initial-setup.desktop.in.in
Normal 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
|
13
data/gnome-initial-setup.service.in
Normal file
13
data/gnome-initial-setup.service.in
Normal 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
|
8
data/gnome-initial-setup.session.conf.in
Normal file
8
data/gnome-initial-setup.session.conf.in
Normal 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
|
4
data/gnome-initial-setup.session.in
Normal file
4
data/gnome-initial-setup.session.in
Normal 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
8
data/initial-setup.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"hasWindows": true,
|
||||
"components": ["networkAgent"],
|
||||
"panel": { "left": [],
|
||||
"center": [],
|
||||
"right": ["a11y", "keyboard", "quickSettings"]
|
||||
}
|
||||
}
|
30
data/meson-add-wants.sh
Executable file
30
data/meson-add-wants.sh
Executable 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
132
data/meson.build
Normal 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
31
gnome-initial-setup.doap
Normal 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>
|
315
gnome-initial-setup/cc-common-language.c
Normal file
315
gnome-initial-setup/cc-common-language.c
Normal 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 (¤t_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;
|
||||
}
|
37
gnome-initial-setup/cc-common-language.h
Normal file
37
gnome-initial-setup/cc-common-language.h
Normal 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
|
512
gnome-initial-setup/gis-assistant.c
Normal file
512
gnome-initial-setup/gis-assistant.c
Normal 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);
|
||||
}
|
7
gnome-initial-setup/gis-assistant.gresource.xml
Normal file
7
gnome-initial-setup/gis-assistant.gresource.xml
Normal 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>
|
48
gnome-initial-setup/gis-assistant.h
Normal file
48
gnome-initial-setup/gis-assistant.h
Normal 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
|
76
gnome-initial-setup/gis-assistant.ui
Normal file
76
gnome-initial-setup/gis-assistant.ui
Normal 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>
|
1031
gnome-initial-setup/gis-driver.c
Normal file
1031
gnome-initial-setup/gis-driver.c
Normal file
File diff suppressed because it is too large
Load diff
132
gnome-initial-setup/gis-driver.h
Normal file
132
gnome-initial-setup/gis-driver.h
Normal 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__ */
|
104
gnome-initial-setup/gis-keyring.c
Normal file
104
gnome-initial-setup/gis-keyring.c
Normal 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);
|
||||
}
|
||||
}
|
35
gnome-initial-setup/gis-keyring.h
Normal file
35
gnome-initial-setup/gis-keyring.h
Normal 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__ */
|
199
gnome-initial-setup/gis-page-header.c
Normal file
199
gnome-initial-setup/gis-page-header.c
Normal 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);
|
||||
}
|
36
gnome-initial-setup/gis-page-header.h
Normal file
36
gnome-initial-setup/gis-page-header.h
Normal 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__ */
|
39
gnome-initial-setup/gis-page-header.ui
Normal file
39
gnome-initial-setup/gis-page-header.ui
Normal 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>
|
422
gnome-initial-setup/gis-page.c
Normal file
422
gnome-initial-setup/gis-page.c
Normal 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);
|
||||
}
|
92
gnome-initial-setup/gis-page.h
Normal file
92
gnome-initial-setup/gis-page.h
Normal 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__ */
|
59
gnome-initial-setup/gis-pkexec.c
Normal file
59
gnome-initial-setup/gis-pkexec.c
Normal 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;
|
||||
}
|
41
gnome-initial-setup/gis-pkexec.h
Normal file
41
gnome-initial-setup/gis-pkexec.h
Normal 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__ */
|
33
gnome-initial-setup/gis-util.c
Normal file
33
gnome-initial-setup/gis-util.c
Normal 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);
|
||||
}
|
19
gnome-initial-setup/gis-util.h
Normal file
19
gnome-initial-setup/gis-util.h
Normal 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);
|
112
gnome-initial-setup/gis-webkit.c
Normal file
112
gnome-initial-setup/gis-webkit.c
Normal 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
|
29
gnome-initial-setup/gis-webkit.h
Normal file
29
gnome-initial-setup/gis-webkit.h
Normal 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
|
318
gnome-initial-setup/gnome-initial-setup-copy-worker.c
Normal file
318
gnome-initial-setup/gnome-initial-setup-copy-worker.c
Normal 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;
|
||||
}
|
383
gnome-initial-setup/gnome-initial-setup.c
Normal file
383
gnome-initial-setup/gnome-initial-setup.c
Normal 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
|
||||
* shouldn’t 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);
|
||||
}
|
44
gnome-initial-setup/gnome-initial-setup.h
Normal file
44
gnome-initial-setup/gnome-initial-setup.h
Normal 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__ */
|
||||
|
115
gnome-initial-setup/meson.build
Normal file
115
gnome-initial-setup/meson.build
Normal 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',
|
||||
)
|
10
gnome-initial-setup/pages/account/account.gresource.xml
Normal file
10
gnome-initial-setup/pages/account/account.gresource.xml
Normal 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>
|
|
@ -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>
|
894
gnome-initial-setup/pages/account/gis-account-page-enterprise.c
Normal file
894
gnome-initial-setup/pages/account/gis-account-page-enterprise.c
Normal 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. It’s 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);
|
||||
}
|
|
@ -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
|
||||
|
280
gnome-initial-setup/pages/account/gis-account-page-enterprise.ui
Normal file
280
gnome-initial-setup/pages/account/gis-account-page-enterprise.ui
Normal 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>
|
644
gnome-initial-setup/pages/account/gis-account-page-local.c
Normal file
644
gnome-initial-setup/pages/account/gis-account-page-local.c
Normal 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);
|
||||
}
|
39
gnome-initial-setup/pages/account/gis-account-page-local.h
Normal file
39
gnome-initial-setup/pages/account/gis-account-page-local.h
Normal 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
|
||||
|
178
gnome-initial-setup/pages/account/gis-account-page-local.ui
Normal file
178
gnome-initial-setup/pages/account/gis-account-page-local.ui
Normal 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>
|
||||
|
310
gnome-initial-setup/pages/account/gis-account-page.c
Normal file
310
gnome-initial-setup/pages/account/gis-account-page.c
Normal 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));
|
||||
}
|
10
gnome-initial-setup/pages/account/gis-account-page.css
Normal file
10
gnome-initial-setup/pages/account/gis-account-page.css
Normal 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;
|
||||
}
|
33
gnome-initial-setup/pages/account/gis-account-page.h
Normal file
33
gnome-initial-setup/pages/account/gis-account-page.h
Normal 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
|
56
gnome-initial-setup/pages/account/gis-account-page.ui
Normal file
56
gnome-initial-setup/pages/account/gis-account-page.ui
Normal 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>
|
32
gnome-initial-setup/pages/account/gis-account-pages.c
Normal file
32
gnome-initial-setup/pages/account/gis-account-pages.c
Normal 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);
|
||||
}
|
33
gnome-initial-setup/pages/account/gis-account-pages.h
Normal file
33
gnome-initial-setup/pages/account/gis-account-pages.h
Normal 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
|
||||
|
33
gnome-initial-setup/pages/account/meson.build
Normal file
33
gnome-initial-setup/pages/account/meson.build
Normal 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()
|
666
gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
Normal file
666
gnome-initial-setup/pages/account/org.freedesktop.realmd.xml
Normal 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>
|
252
gnome-initial-setup/pages/account/um-photo-dialog.c
Normal file
252
gnome-initial-setup/pages/account/um-photo-dialog.c
Normal 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;
|
||||
}
|
46
gnome-initial-setup/pages/account/um-photo-dialog.h
Normal file
46
gnome-initial-setup/pages/account/um-photo-dialog.h
Normal 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
|
878
gnome-initial-setup/pages/account/um-realm-manager.c
Normal file
878
gnome-initial-setup/pages/account/um-realm-manager.c
Normal 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,
|
||||
_("Couldn’t 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;
|
||||
}
|
108
gnome-initial-setup/pages/account/um-realm-manager.h
Normal file
108
gnome-initial-setup/pages/account/um-realm-manager.h
Normal 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__ */
|
389
gnome-initial-setup/pages/account/um-utils.c
Normal file
389
gnome-initial-setup/pages/account/um-utils.c
Normal 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 isn’t 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 isn’t 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 can’t 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);
|
||||
}
|
50
gnome-initial-setup/pages/account/um-utils.h
Normal file
50
gnome-initial-setup/pages/account/um-utils.h
Normal 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
|
43
gnome-initial-setup/pages/keyboard/cc-ibus-utils.c
Normal file
43
gnome-initial-setup/pages/keyboard/cc-ibus-utils.c
Normal 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 */
|
29
gnome-initial-setup/pages/keyboard/cc-ibus-utils.h
Normal file
29
gnome-initial-setup/pages/keyboard/cc-ibus-utils.h
Normal 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__ */
|
908
gnome-initial-setup/pages/keyboard/cc-input-chooser.c
Normal file
908
gnome-initial-setup/pages/keyboard/cc-input-chooser.c
Normal 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;
|
||||
}
|
65
gnome-initial-setup/pages/keyboard/cc-input-chooser.h
Normal file
65
gnome-initial-setup/pages/keyboard/cc-input-chooser.h
Normal 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__ */
|
538
gnome-initial-setup/pages/keyboard/gis-keyboard-page.c
Normal file
538
gnome-initial-setup/pages/keyboard/gis-keyboard-page.c
Normal 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);
|
||||
}
|
70
gnome-initial-setup/pages/keyboard/gis-keyboard-page.h
Normal file
70
gnome-initial-setup/pages/keyboard/gis-keyboard-page.h
Normal 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 */
|
36
gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui
Normal file
36
gnome-initial-setup/pages/keyboard/gis-keyboard-page.ui
Normal 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>
|
23
gnome-initial-setup/pages/keyboard/input-chooser.ui
Normal file
23
gnome-initial-setup/pages/keyboard/input-chooser.ui
Normal 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>
|
|
@ -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>
|
14
gnome-initial-setup/pages/keyboard/meson.build
Normal file
14
gnome-initial-setup/pages/keyboard/meson.build
Normal 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'
|
||||
)
|
597
gnome-initial-setup/pages/language/cc-language-chooser.c
Normal file
597
gnome-initial-setup/pages/language/cc-language-chooser.c
Normal 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;
|
||||
}
|
63
gnome-initial-setup/pages/language/cc-language-chooser.h
Normal file
63
gnome-initial-setup/pages/language/cc-language-chooser.h
Normal 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__ */
|
105
gnome-initial-setup/pages/language/cc-util.c
Normal file
105
gnome-initial-setup/pages/language/cc-util.c
Normal 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;
|
||||
}
|
28
gnome-initial-setup/pages/language/cc-util.h
Normal file
28
gnome-initial-setup/pages/language/cc-util.h
Normal 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
|
310
gnome-initial-setup/pages/language/gis-language-page.c
Normal file
310
gnome-initial-setup/pages/language/gis-language-page.c
Normal 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);
|
||||
}
|
57
gnome-initial-setup/pages/language/gis-language-page.h
Normal file
57
gnome-initial-setup/pages/language/gis-language-page.h
Normal 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__ */
|
48
gnome-initial-setup/pages/language/gis-language-page.ui
Normal file
48
gnome-initial-setup/pages/language/gis-language-page.ui
Normal 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>
|
242
gnome-initial-setup/pages/language/gis-welcome-widget.c
Normal file
242
gnome-initial-setup/pages/language/gis-welcome-widget.c
Normal 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);
|
||||
}
|
56
gnome-initial-setup/pages/language/gis-welcome-widget.h
Normal file
56
gnome-initial-setup/pages/language/gis-welcome-widget.h
Normal 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__ */
|
14
gnome-initial-setup/pages/language/gis-welcome-widget.ui
Normal file
14
gnome-initial-setup/pages/language/gis-welcome-widget.ui
Normal 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>
|
22
gnome-initial-setup/pages/language/language-chooser.ui
Normal file
22
gnome-initial-setup/pages/language/language-chooser.ui
Normal 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>
|
10
gnome-initial-setup/pages/language/language.gresource.xml
Normal file
10
gnome-initial-setup/pages/language/language.gresource.xml
Normal 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>
|
16
gnome-initial-setup/pages/language/meson.build
Normal file
16
gnome-initial-setup/pages/language/meson.build
Normal 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',
|
||||
)
|
20
gnome-initial-setup/pages/meson.build
Normal file
20
gnome-initial-setup/pages/meson.build
Normal 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
|
827
gnome-initial-setup/pages/network/gis-network-page.c
Normal file
827
gnome-initial-setup/pages/network/gis-network-page.c
Normal 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);
|
||||
}
|
55
gnome-initial-setup/pages/network/gis-network-page.h
Normal file
55
gnome-initial-setup/pages/network/gis-network-page.h
Normal 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__ */
|
100
gnome-initial-setup/pages/network/gis-network-page.ui
Normal file
100
gnome-initial-setup/pages/network/gis-network-page.ui
Normal 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>
|
12
gnome-initial-setup/pages/network/meson.build
Normal file
12
gnome-initial-setup/pages/network/meson.build
Normal 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
Loading…
Add table
Add a link
Reference in a new issue