1
0
Fork 0

Adding upstream version 1:48.2.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-22 19:52:18 +02:00
parent 446e9989ce
commit 0a49575b51
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
1390 changed files with 1508331 additions and 0 deletions

12
.clang-format Normal file
View file

@ -0,0 +1,12 @@
# See https://wiki.apertis.org/Guidelines/Coding_conventions#Code_formatting
BasedOnStyle: GNU
AlwaysBreakAfterDefinitionReturnType: All
BreakBeforeBinaryOperators: None
BinPackParameters: false
SpaceAfterCStyleCast: true
PointerAlignment: Right
# Our column limit is actually 80, but setting that results in clang-format
# making a lot of dubious hanging-indent choices; disable it and assume the
# developer will line wrap appropriately. clang-format will still check
# existing hanging indents.
ColumnLimit: 0

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
__pycache__
_build/
**/*~
/subprojects/*
!/subprojects/*.wrap
.flatpak-builder/
.cache/clangd/

369
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,369 @@
include:
- project: Infrastructure/freedesktop-ci-templates
file: templates/fedora.yml
ref: 765bb77a5e1274b0daeb14344e7caa6cd3876232
- component: gitlab.gnome.org/GNOME/citemplates/release-service@master
inputs:
dist-job-name: release-build
tarball-artifact-path: $TARBALL_ARTIFACT_PATH
variables:
FDO_UPSTREAM_REPO: gnome/gnome-control-center
MESON_BUILD_DIR: _build
TARBALL_ARTIFACT_PATH: "$MESON_BUILD_DIR/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz"
stages:
- prepare
- build
- test
- manual
- deploy
.Build Logs: &build_logs
name: build_logs
when: always
paths:
- ./*.log
- $MESON_BUILD_DIR/meson-logs/
.Build Output: &build_output
artifacts:
name: build_output
when: always
paths:
- ./*.log
- $MESON_BUILD_DIR/
expire_in: 3h30min
.Show Info: &environment_information
build-aux/ci/ci-helper.sh "INFO" &&
build-aux/ci/ci-helper.sh "GIT_INFO"
.Build Procedure: &build_procedure
echo "== Building ==" &&
meson setup $MESON_BUILD_DIR ${BUILD_OPTS} -Dprofile=development -Dwarning_level=2 &&
meson compile -C $MESON_BUILD_DIR 2>&1 | tee compilation.log
.Run Tests: &run_tests
export GSK_RENDERER=ngl &&
echo "== Testing ==" &&
meson test -C $MESON_BUILD_DIR --verbose --no-stdsplit --no-rebuild
.fedora.container.common:
variables:
# When branching a stable release, change 'main' to the
# release branch name to ensure that a new image will
# be created, tailored for the stable branch.
# Should probably also switch away from 'rawhide',
# to stable fedora branch as well.
BRANCH_NAME: 'gnome-48'
CONTAINER_TAG: '2025-05-20.0'
FEDORA_VERSION: 42
# Derive FDO variables from this automatically.
# DO NOT edit, instead change the variables above
FDO_REPO_SUFFIX: '${BRANCH_NAME}'
FDO_DISTRIBUTION_TAG: '${CONTAINER_TAG}-fedora-${FEDORA_VERSION}'
FDO_DISTRIBUTION_VERSION: '${FEDORA_VERSION}'
#############################################
# Create CI Docker Images #
#############################################
# 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
# NOTES:
# - most packages are needed for testing
# - glibc-langpack-en is needed for sorting in run-style-check-diff.sh
# - libsass-devel, sassc are often needed for building libadwaita, gtk, etc.
FDO_DISTRIBUTION_PACKAGES: >-
dnf5-plugins
gawk
git
llvm clang libasan libtsan libubsan compiler-rt gcovr
meson
clang-tools-extra
glibc-langpack-en
python3-dbusmock
xorg-x11-server-Xvfb
mesa-dri-drivers
libsass-devel sassc
FDO_DISTRIBUTION_EXEC: |-
dnf builddep -y gnome-control-center && \
dnf remove -y systemtap-runtime
##
# Stage: Build
#
# Checks if GNOME Control Center is properly building and installing. This is the
# most important stage of the CI, and no MR should ever be merged if it breaks
# any of them.
##
build:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
stage: build
script:
- *environment_information
- *build_procedure
- echo "== Installing =="
- meson install -C $MESON_BUILD_DIR
- echo "== Report =="
- build-aux/ci/ci-helper.sh "WARNINGS"
<<: *build_output
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
# Same as build but with various off-by-default options enabled
conditional builds:
extends:
- build
artifacts:
# Only save build logs for these
<<: *build_logs
parallel:
matrix:
- BUILD_OPTS: '-Dmalcontent=true'
- BUILD_OPTS: '-Ddocumentation=true'
##
# Stage: Test
#
# Runs static checks.
# Runs the unit tests.
##
style-check-diff:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
needs:
- build.container.fedora@x86_64
stage: test
when: always
allow_failure: true
script:
- .gitlab-ci/run-style-check-diff.sh
check-potfiles:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
needs:
- build.container.fedora@x86_64
stage: test
when: always
variables:
GIT_SUBMODULE_STRATEGY: normal
script:
- .gitlab-ci/check-potfiles.sh
test:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
stage: test
needs:
- build
script:
- *environment_information
- *run_tests
artifacts:
<<: *build_logs
reports:
junit: '$MESON_BUILD_DIR/meson-logs/testlog.junit.xml'
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
- $CI_COMMIT_TITLE =~ /^Update.*translation$/
release-build:
stage: build
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
script:
- meson subprojects download gvc libgxdp
- meson setup $MESON_BUILD_DIR --wrap-mode=nodownload
- meson dist -C $MESON_BUILD_DIR --no-tests --include-subprojects
artifacts:
paths:
- $TARBALL_ARTIFACT_PATH
# Runs the coverage test.
coverage:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
dependencies:
- build.container.fedora@x86_64
stage: test
variables:
BUILD_OPTS: "-Db_coverage=true"
coverage: '/^Lines:.\d+.\d+.(\d+\.\d+\%)/'
only:
- main@GNOME/gnome-control-center
script:
- *environment_information
- *build_procedure
- *run_tests
- ninja -C $MESON_BUILD_DIR coverage-html
# Parse the report to get the coverage result
- |
echo == Coverage ==
sed -e 's/<[^>]*>//g' $MESON_BUILD_DIR/meson-logs/coveragereport/index.html | tr -d ' \t' | grep -A3 -P '^Lines:$' | tr '\n' ' '; echo
artifacts:
<<: *build_logs
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
- $CI_COMMIT_TITLE =~ /^Update.*translation$/
##
# Stage: Deploy
#
# Publishes release artifacts and the Coverage Report generated above
##
pages:
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
stage: deploy
dependencies:
- coverage
script:
- mv $MESON_BUILD_DIR/meson-logs/coveragereport/ public/
artifacts:
paths:
- public
only:
- main@GNOME/gnome-control-center
except:
variables:
- $CRON_TASK == "BUILD_CI_IMAGES"
- $CI_COMMIT_TITLE =~ /^Update.*translation$/
##
# Stage: Manual
#
# Creates a flatpak
##
flatpak:
stage: manual
image: quay.io/gnome_infrastructure/gnome-runtime-images:gnome-master
artifacts:
name: package
paths:
- $(pwd)/*.flatpak
expire_in: 7 days
variables:
OLD_APPID: "org.gnome.Settings"
APPID: "org.gnome.Settings.Devel"
BUNDLE: "org.gnome.Settings.Devel.flatpak"
MANIFEST_PATH: "build-aux/flatpak/org.gnome.Settings.Devel.json"
PROJECT_ID: "org.gnome.Settings"
PROJECT_NAME: "gnome-control-center.git"
RUNTIME_REPO: "https://sdk.gnome.org/gnome-nightly.flatpakrepo"
script:
- echo "== Flatpak packaging =="
# Make it a develoment manifest
- sed -i -n "p; s/$PROJECT_NAME//p" ${MANIFEST_PATH}
- >
sed -i "s,\"app-id\" : \"$PROJECT_ID\",\"app-id\" : \"<<ID>>\",g" ${MANIFEST_PATH}
- >
sed -i "s,\"url\" : \"https://gitlab.gnome.org/GNOME/$PROJECT_NAME\",\"branch\" : \"<<current>>\"\,,g" ${MANIFEST_PATH}
- >
sed -i "s,\"url\" : \"https://gitlab.gnome.org/GNOME/\",\"path\" : \".\",g" ${MANIFEST_PATH}
# Adjust the manifest to HEAD
- sed -i "s,<<ID>>,$APPID,g" ${MANIFEST_PATH}
- sed -i "s,<<current>>,origin/$CI_COMMIT_REF_NAME,g" ${MANIFEST_PATH}
# Update the icon name
- mv data/icons/hicolor/scalable/apps/$OLD_APPID.svg data/icons/hicolor/scalable/apps/$APPID.svg
- mv data/icons/hicolor/symbolic/apps/$OLD_APPID-symbolic.svg data/icons/hicolor/symbolic/apps/$APPID-symbolic.svg
- flatpak-builder --bundle-sources --disable-rofiles-fuse --repo=devel build ${MANIFEST_PATH}
- flatpak build-bundle devel ${BUNDLE} --runtime-repo=${RUNTIME_REPO} ${APPID}
cache:
paths:
- .flatpak-builder/cache
when: manual
except:
- tags
- gnome-3-.*
- main@GNOME/gnome-control-center
# Runs the sanitizers [address, thread, undefined, and memory].
.sanitizer: &sanitizer
extends:
- '.fdo.suffixed-image@fedora'
- '.fedora.container.common'
dependencies:
- build.container.fedora@x86_64
stage: manual
when: manual
script:
- *environment_information
- *build_procedure
- *run_tests
artifacts:
<<: *build_logs
except:
variables:
- $CI_PIPELINE_SOURCE == "schedule"
- $CI_COMMIT_TITLE =~ /^Update.*translation$/
asan:
<<: *sanitizer
# ASAN requires debugging capabilities
tags: [ asan ]
variables:
BUILD_OPTS: "-Db_sanitize=address"
LSAN_OPTIONS: "suppressions=${CI_PROJECT_DIR}/build-aux/ci/lsan.supp"
tsan:
<<: *sanitizer
variables:
BUILD_OPTS: "-Db_sanitize=thread"
ubsan:
<<: *sanitizer
variables:
BUILD_OPTS: "-Db_sanitize=undefined"
msan:
<<: *sanitizer
variables:
BUILD_OPTS: "-Db_sanitize=memory"
CC: "clang"

88
.gitlab-ci/check-potfiles.sh Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env bash
srcdirs="global-shortcuts-provider panels shell subprojects/gvc"
uidirs=$srcdirs
desktopdirs=$srcdirs
policydirs=$srcdirs
xmldirs=$srcdirs
# find source files that contain gettext functions with literal or GETTEXT_PACKAGE argument
files=$(grep -ElRs --include='*.c' 'gettext2? ?\(("|GETTEXT_PACKAGE,)' $srcdirs)
# find source files that contain gettext macros
files="$files "$(grep -lRs --include='*.c' --include='*.h' '[^I_)]_(' $srcdirs)
# find ui files that contain translatable string
files="$files "$(grep -lRis --include='*.ui' 'translatable="[ty1]' $uidirs)
# find .desktop files
files="$files "$(find $desktopdirs -name '*.desktop*')
# find .policy files
files="$files "$(find $policydirs -name '*.policy*')
# find .xml.in and .gschema.xml files
files="$files "$(find $xmldirs -not -name '*.gresource.xml*' \
-name '*.xml.in*' -o \
-name '*.gschema.xml')
# filter out excluded files
if [ -f po/POTFILES.skip ]; then
files=$(for f in $files; do
! grep -q "^$f" po/POTFILES.skip && echo "$f"
done)
fi
# find those that aren't listed in POTFILES.in
missing=$(for f in $files; do
! grep -q "^$f" po/POTFILES.in && echo "$f"
done)
# find those that are listed in POTFILES.in but do not contain translatable strings
invalid=$(while IFS= read -r f; do
! grep -q "$f" <<< "$files" && echo "$f"
done < <(grep '^[^#]' po/POTFILES.in))
# find out if POTFILES.in is sorted correctly, ignoring empty lines
sorted=$(grep '^[^#]' po/POTFILES.in | \
LC_ALL=en_US.UTF-8 sort -cu 2>&1 >/dev/null | \
awk -F ': *' '{print $5}')
if [ ${#missing} -eq 0 ] && [ ${#invalid} -eq 0 ] && [ ${#sorted} -eq 0 ]; then
exit 0
fi
if [ ${#missing} -ne 0 ]; then
cat >&2 << EOT
The following files are missing from po/POTFILES.in or po/POTFILES.skip:
EOT
for f in $missing; do
echo " $f" >&2
done
fi
if [ ${#invalid} -ne 0 ]; then
cat >&2 << EOT
The following files are in po/POTFILES.in but are missing, skipped or not translatable:
EOT
for f in $invalid; do
echo " $f" >&2
done
fi
if [ ${#sorted} -ne 0 ]; then
cat >&2 << EOT
The following file is not sorted properly in po/POTFILES.in:
EOT
echo " $sorted" >&2
fi
echo >&2
exit 1

133
.gitlab-ci/clang-format-diff.py Executable file
View file

@ -0,0 +1,133 @@
#!/usr/bin/env python3
#
# === clang-format-diff.py - ClangFormat Diff Reformatter ---*- python -*-=== #
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===---------------------------------------------------------------------=== #
"""
This script reads input from a unified diff and reformats all the changed
lines. This is useful to reformat all the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i
svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
"""
from __future__ import absolute_import, division, print_function
import argparse
import difflib
import re
import subprocess
import sys
if sys.version_info.major >= 3:
from io import StringIO
else:
from io import BytesIO as StringIO
def main():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-i', action='store_true', default=False,
help='apply edits to files instead of displaying a '
'diff')
parser.add_argument('-p', metavar='NUM', default=0,
help='strip the smallest prefix containing P slashes')
parser.add_argument('-regex', metavar='PATTERN', default=None,
help='custom pattern selecting file paths to reformat '
'(case sensitive, overrides -iregex)')
parser.add_argument('-iregex', metavar='PATTERN',
default=r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|m|mm|inc'
r'|js|ts|proto|protodevel|java|cs)',
help='custom pattern selecting file paths to reformat '
'(case insensitive, overridden by -regex)')
parser.add_argument('-sort-includes', action='store_true', default=False,
help='let clang-format sort include blocks')
parser.add_argument('-v', '--verbose', action='store_true',
help='be more verbose, ineffective without -i')
parser.add_argument('-style',
help='formatting style to apply (LLVM, Google, '
'Chromium, Mozilla, WebKit)')
parser.add_argument('-binary', default='clang-format',
help='location of binary to use for clang-format')
args = parser.parse_args()
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
if match:
filename = match.group(2)
if filename is None:
continue
if args.regex is not None:
if not re.match('^%s$' % args.regex, filename):
continue
else:
if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
continue
match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(3):
line_count = int(match.group(3))
if line_count == 0:
continue
end_line = start_line + line_count - 1
lines_by_file.setdefault(filename, []).extend(
['-lines', str(start_line) + ':' + str(end_line)])
# Reformat files containing changes in place.
# We need to count amount of bytes generated in the output of
# clang-format-diff. If clang-format-diff doesn't generate any bytes it
# means there is nothing to format.
format_line_counter = 0
for filename, lines in lines_by_file.items():
if args.i and args.verbose:
print('Formatting {}'.format(filename))
command = [args.binary, filename]
if args.i:
command.append('-i')
if args.sort_includes:
command.append('-sort-includes')
command.extend(lines)
if args.style:
command.extend(['-style', args.style])
p = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=None,
stdin=subprocess.PIPE,
universal_newlines=True)
stdout, _ = p.communicate()
if p.returncode != 0:
sys.exit(p.returncode)
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = StringIO(stdout).readlines()
diff = difflib.unified_diff(code, formatted_code,
filename, filename,
'(before formatting)',
'(after formatting)')
diff_string = ''.join(diff)
if diff_string:
format_line_counter += sys.stdout.write(diff_string)
if format_line_counter > 0:
sys.exit(1)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,62 @@
#!/bin/bash
set -e
ancestor_horizon=31 # days (one month)
# Recently, git is picky about directory ownership. Tell it not to worry.
git config --global --add safe.directory "$PWD"
# We need to add a new remote for the upstream target branch, since this script
# could be running in a personal fork of the repository which has out of date
# branches.
#
# Limit the fetch to a certain date horizon to limit the amount of data we get.
# If the branch was forked from origin/main before this horizon, it should
# probably be rebased.
if ! git ls-remote --exit-code upstream >/dev/null 2>&1 ; then
git remote add upstream https://gitlab.gnome.org/GNOME/gnome-control-center.git
fi
# Work out the newest common ancestor between the detached HEAD that this CI job
# has checked out, and the upstream target branch (which will typically be
# `upstream/main` or `upstream/glib-2-62`).
# `${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}` or `${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}`
# are only defined if were running in a merge request pipeline,
# fall back to `${CI_DEFAULT_BRANCH}` or `${CI_COMMIT_BRANCH}` respectively
# otherwise.
source_branch="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-${CI_COMMIT_BRANCH}}"
target_branch="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}"
git fetch --shallow-since="$(date --date="${ancestor_horizon} days ago" +%Y-%m-%d)" origin "${source_branch}"
git fetch --shallow-since="$(date --date="${ancestor_horizon} days ago" +%Y-%m-%d)" upstream "${target_branch}"
newest_common_ancestor_sha=$(git merge-base upstream/${target_branch} origin/${source_branch})
if [ -z "${newest_common_ancestor_sha}" ]; then
echo "Couldnt find common ancestor with upstream main branch. This typically"
echo "happens if you branched from main a long time ago. Please update"
echo "your clone, rebase, and re-push your branch."
exit 1
fi
git diff -U0 --no-color "${newest_common_ancestor_sha}" | .gitlab-ci/clang-format-diff.py -binary "clang-format" -p1
exit_status=$?
# The style check is not infallible. The clang-format configuration cannot
# perfectly describe GNOME Settings coding style: in particular, it cannot align
# function arguments. The documented coding style for GNOME Settings takes priority over
# clang-format suggestions. Hopefully we can eventually improve clang-format to
# be configurable enough for our coding style. Thats why this CI check is OK
# to fail: the idea is that people can look through the output and ignore it if
# its wrong. (That situation can also happen if someone touches pre-existing
# badly formatted code and it doesnt make sense to tidy up the wider coding
# style with the changes theyre making.)
echo ""
echo "Note that clang-format output is advisory and cannot always match the"
echo "GNOME Settings coding style, documented at:"
echo " https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/docs/CODING_STYLE.md"
echo "Warnings from this tool can be ignored in favour of the documented "
echo "coding style, or in favour of matching the style of existing"
echo "surrounding code."
exit ${exit_status}

View file

@ -0,0 +1,32 @@
<!--
Not following the communication guidelines [1] will mean your issue or comment
will be removed. Read it carefully before submitting this issue.
[1] https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/docs/CODE_OF_CONDUCT.md#communication-guidelines
Detailed description of the issue. Put as much information as you can, potentially
with images showing the issue.
-->
## Relevant information
* GNOME Settings version
<!-- Find with the command "gnome-control-center --version" -->
* Operating system (distribution)
<!-- Find with the command "cat /etc/os-release" -->
* Error message
<!-- Reproduce the issue while running with the command "G_MESSAGES_DEBUG=all gnome-control-center" -->
* Backtrace (if you are experiencing a crash)
<!-- Check for instructions at https://handbook.gnome.org/issues/stack-traces.html -->
* Screenshots
## Steps to reproduce:
1. Open GNOME Settings
2. Change X to something else
3. ...

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "subprojects/gvc"]
path = subprojects/gvc
url = https://gitlab.gnome.org/GNOME/libgnome-volume-control.git

340
COPYING Normal file
View file

@ -0,0 +1,340 @@
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.

11467
NEWS Normal file

File diff suppressed because it is too large Load diff

27
README.md Normal file
View file

@ -0,0 +1,27 @@
[![Build Status](https://gitlab.gnome.org/GNOME/gnome-control-center/badges/main/pipeline.svg)](https://gitlab.gnome.org/GNOME/gnome-control-center/pipelines)
[![Coverage report](https://gitlab.gnome.org/GNOME/gnome-control-center/badges/main/coverage.svg)](https://gnome.pages.gitlab.gnome.org/gnome-control-center/)
[![License](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/COPYING)
GNOME Settings
====================
GNOME Settings is GNOME's main interface for configuration of various aspects of your desktop.
If you are looking for usage tips and instructions, you can find it at the [User Documentation](https://help.gnome.org/users/gnome-help/stable/prefs.html) (translated in various languages).
## Reporting Issues
Before reporting any bugs or opening feature requests, [read the communication guidelines](https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/docs/CODE_OF_CONDUCT.md#communication-guidelines).
Report issues to the [GNOME issue tracking system](https://gitlab.gnome.org/GNOME/gnome-control-center/issues).
## Feature Requests
For feature requests or conceptual changes, please start a topic on [GNOME Discourse](https://discourse.gnome.org/tags/settings).
## Contributing
See [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) for details on the contribution process, and [`docs/CODING_STYLE.md`](docs/CODING_STYLE.md)
for the coding style guidelines.
Visit the [Settings development wiki](https://gitlab.gnome.org/GNOME/gnome-control-center/-/wikis/home) for more information.

61
build-aux/ci/ci-helper.sh Executable file
View file

@ -0,0 +1,61 @@
#!/bin/bash -e
function do_print_labels(){
if [[ -n "${1}" ]]; then
label_len=${#1}
span=$(((54 - $label_len) / 2))
echo
echo "= ======================================================== ="
printf "%s %${span}s %s %${span}s %s\n" "=" "" "$1" "" "="
echo "= ======================================================== ="
else
echo "= ========================= Done ========================= ="
echo
fi
}
function do_show_info(){
local compiler=gcc
echo -n "Processors: "; grep -c ^processor /proc/cpuinfo
grep ^MemTotal /proc/meminfo
id; uname -a
printenv
echo '-----------------------------------------'
cat /etc/*-release
echo '-----------------------------------------'
if [[ ! -z $CC ]]; then
compiler=$CC
fi
echo 'Compiler version'
$compiler --version
echo '-----------------------------------------'
$compiler -dM -E -x c /dev/null
echo '-----------------------------------------'
}
function do_check_warnings(){
cat compilation.log | grep "warning:" | awk '{total+=1}END{print "Total number of warnings: "total}'
}
# ----------- -----------
if [[ $1 == "INFO" ]]; then
do_print_labels 'Build environment '
do_show_info
do_print_labels
elif [[ $1 == "GIT_INFO" ]]; then
do_print_labels 'Commit'
git log --pretty=format:"%h %cd %s" -1; echo
do_print_labels
elif [[ $1 == "WARNINGS" ]]; then
do_print_labels 'Warning Report '
do_check_warnings
do_print_labels
fi

19
build-aux/ci/lsan.supp Normal file
View file

@ -0,0 +1,19 @@
# https://bugs.freedesktop.org/show_bug.cgi?id=105466
leak:libfontconfig.so.1
# Unknown and unreported bugs
leak:__GI___vasprintf_chk
leak:__interceptor_malloc
leak:__alloc_dir
leak:libglib-2.0.so.0
# Needing attention
leak:<unknown module>
leak:test_timezone_gfx
########
# __GI___vasprintf_chk /usr/src/debug/glibc-2.27.9000-432-g104502102c/debug/vasprintf_chk.c:80
# __alloc_dir ../sysdeps/posix/opendir.c:118
# g_malloc (/lib64/libglib-2.0.so.0+0x523c5)
# g_realloc (/lib64/libglib-2.0.so.0+0x5247d)
########

View file

@ -0,0 +1,22 @@
From ba5e0451b51c983e40afd123b6e0d3eddb55e610 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Sat, 24 Mar 2018 17:37:02 +0000
Subject: linux: Include <sys/sysmacros.h> for major() macro.
Since glibc 2.27 this header is required.
---
libparted/arch/linux.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/libparted/arch/linux.c b/libparted/arch/linux.c
index 31b98ab..7e86b51 100644
--- a/libparted/arch/linux.c
+++ b/libparted/arch/linux.c
@@ -41,6 +41,7 @@
#include <sys/utsname.h> /* for uname() */
#include <scsi/scsi.h>
#include <assert.h>
+#include <sys/sysmacros.h>
#ifdef ENABLE_DEVICE_MAPPER
#include <libdevmapper.h>
#endif

View file

@ -0,0 +1,939 @@
{
"app-id" : "org.gnome.Settings.Devel",
"runtime" : "org.gnome.Sdk",
"runtime-version" : "master",
"sdk" : "org.gnome.Sdk",
"command" : "gnome-control-center",
"tags" : [
"devel"
],
"desktop-file-name-prefix" : "(Development) ",
"finish-args" : [
"--device=dri",
"--env=DCONF_USER_CONFIG_DIR=.config/dconf",
"--filesystem=host",
"--own-name=org.gnome.SessionManager",
"--share=ipc",
"--share=network",
"--socket=x11",
"--socket=pulseaudio",
"--socket=session-bus",
"--socket=system-bus",
"--socket=wayland"
],
"build-options" : {
"cflags" : "-O2 -g",
"cxxflags" : "-O2 -g",
"env" : {
"V" : "1"
},
"build-args": [
"--share=network"
]
},
"x-run-args" : [
"--verbose"
],
"cleanup" : [
"/include",
"/share/aclocal",
"/man",
"/share/man",
"/share/gtk-doc",
"/share/vala",
"*.la",
"*.a"
],
"modules" : [
{
"name" : "pwquality",
"buildsystem" : "autotools",
"config-opts" : [
"--disable-python-bindings"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/libpwquality/libpwquality.git"
}
]
},
{
"name" : "intltool",
"cleanup" : [ "*" ],
"sources" : [
{
"type" : "archive",
"url" : "https://launchpad.net/intltool/trunk/0.51.0/+download/intltool-0.51.0.tar.gz",
"sha256" : "67c74d94196b153b774ab9f89b2fa6c6ba79352407037c8c14d5aeb334e959cd"
}
]
},
{
"name" : "polkit",
"buildsystem" : "meson",
"config-opts" : [
"-Dauthfw=shadow"
],
"rm-configure" : true,
"cleanup" : [
"/bin/*",
"/etc/pam.d",
"/etc/dbus-1",
"/share/dbus-1/system-services/*",
"/share/polkit-1",
"/lib/polkit-1"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.freedesktop.org/polkit/polkit.git",
"tag" : "124",
"commit" : "82f0924dc0eb23b9df68e88dbaf9e07c81940a5a"
}
]
},
{
"name" : "json-c",
"buildsystem" : "cmake",
"sources" : [
{
"type" : "archive",
"url" : "https://github.com/json-c/json-c/archive/refs/tags/json-c-0.17-20230812.tar.gz",
"sha256" : "024d302a3aadcbf9f78735320a6d5aedf8b77876c8ac8bbb95081ca55054c7eb"
}
]
},
{
"name" : "accountsservice",
"buildsystem" : "meson",
"config-opts" : [
"-Dsystemdsystemunitdir=no",
"-Dintrospection=false",
"-Dvapi=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.freedesktop.org/accountsservice/accountsservice",
"branch" : "main"
}
]
},
{
"name" : "libusb1",
"buildsystem" : "autotools",
"config-opts" : [
"--disable-udev"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/libusb/libusb.git"
}
]
},
{
"name" : "gusb",
"buildsystem" : "meson",
"config-opts" : [
"-Ddocs=false",
"-Dtests=false",
"-Dvapi=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/hughsie/libgusb.git",
"branch" : "main"
}
]
},
{
"name" : "udev",
"config-opts" : [
"--disable-hwdb",
"--disable-logging",
"--disable-gudev",
"--disable-introspection",
"--disable-keymap",
"--disable-mtd_probe"
],
"cleanup" : [
"/include",
"/etc",
"/libexec",
"/sbin",
"/lib/pkgconfig",
"/man",
"/share/aclocal",
"/share/doc",
"/share/gtk-doc",
"/share/man",
"/share/pkgconfig",
"*.la",
"*.a"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/gentoo/eudev.git"
}
]
},
{
"name" : "colord",
"buildsystem" : "meson",
"config-opts" : [
"-Dargyllcms_sensor=false",
"-Dbash_completion=false",
"-Dman=false",
"-Dudev_rules=false",
"-Dsystemd=false",
"-Dvapi=false",
"-Ddocs=false",
"-Dtests=false",
"-Dinstalled_tests=false"
],
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://github.com/hughsie/colord.git"
}
]
},
{
"name" : "colord-gtk",
"buildsystem" : "meson",
"config-opts" : [
"-Dman=false"
],
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://github.com/hughsie/colord-gtk.git"
}
]
},
{
"name" : "rest",
"buildsystem" : "meson",
"config-opts" : [
"-Dgtk_doc=false"
],
"sources" : [
{
"type" : "git",
"branch" : "master",
"url" : "https://gitlab.gnome.org/GNOME/librest.git"
}
]
},
{
"name" : "gnome-online-accounts",
"buildsystem" : "meson",
"build-options" : {
"cflags" : "-Wno-error"
},
"config-opts" : [
"-Ddocumentation=false",
"-Dintrospection=false",
"-Dvapi=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/gnome-online-accounts.git"
}
],
"modules" : [
{
"name" : "keyutils",
"buildsystem" : "simple",
"build-commands" : [
"make NO_ARLIB=1 BINDIR=/app/bin SBINDIR=/app/sbin LIBDIR=/app/lib INCLUDEDIR=/app/include MANDIR=/app/share/man SHAREDIR=/app install"
],
"sources" : [
{
"type" : "git",
"url" : "https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/keyutils.git"
}
]
}
]
},
{
"name" : "gnome-desktop",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/gnome-desktop.git"
}
]
},
{
"name" : "geocode-glib",
"buildsystem" : "meson",
"config-opts" : [
"-Denable-installed-tests=false",
"-Denable-introspection=false",
"-Denable-gtk-doc=false",
"-Dsoup2=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/geocode-glib.git"
}
]
},
{
"name" : "libgweather",
"buildsystem" : "meson",
"config-opts" : [
"-Denable_vala=false",
"-Dgtk_doc=false",
"-Dintrospection=false",
"-Dtests=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/libgweather.git",
"branch" : "main"
}
]
},
{
"name" : "upower",
"buildsystem" : "meson",
"config-opts" : [
"-Dsystemdsystemunitdir=/app/lib/systemd/system",
"-Dudevrulesdir=/app/lib/udev",
"-Dudevhwdbdir=/app/lib/udev",
"-Dgtk-doc=false",
"-Dman=false",
"-Dintrospection=disabled"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.freedesktop.org/upower/upower.git",
"tag" : "v1.90.6"
}
]
},
{
"name" : "libwacom",
"buildsystem" : "meson",
"config-opts" : [
"-Ddocumentation=disabled",
"-Dtests=disabled"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/linuxwacom/libwacom.git"
}
]
},
{
"name" : "libndp",
"buildsystem" : "autotools",
"sources" : [
{
"type" : "archive",
"url" : "http://libndp.org/files/libndp-1.6.tar.gz",
"sha256" : "0c7dfa84e013bd5e569ef2c6292a6f72cfaf14f4ff77a77425e52edc33ffac0e"
}
]
},
{
"name" : "NetworkManager",
"buildsystem" : "meson",
"build-options" : {
"cflags" : "-ltinfo",
"cxxflags" : "-ltinfo"
},
"config-opts" : [
"-Dlibaudit=no",
"-Ddbus_conf_dir=/app/etc/dbus-1/system.d",
"-Ddnsmasq=/usr/bin/true",
"-Ddocs=false",
"-Dintrospection=false",
"-Diptables=/usr/bin/true",
"-Dlibpsl=false",
"-Dmodem_manager=false",
"-Dnm_cloud_setup=false",
"-Dnmtui=false",
"-Dovs=false",
"-Dppp=false",
"-Dqt=false",
"-Dselinux=false",
"-Dsession_tracking=no",
"-Dsystemdsystemunitdir=/app/lib/systemd/system",
"-Dudev_dir=/app/lib/udev",
"-Dsystemd_journal=false",
"-Dtests=no",
"-Dvapi=false"
],
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git"
}
]
},
{
"name" : "mobile-broadband-provider-info",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info.git"
}
]
},
{
"name" : "libnma",
"buildsystem" : "meson",
"config-opts" : [
"-Dlibnma_gtk4=true",
"-Dgtk_doc=false",
"-Dintrospection=false",
"-Dvapi=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/libnma.git",
"branch" : "main"
}
]
},
{
"name" : "network-manager-applet",
"buildsystem" : "meson",
"config-opts" : [
"-Dappindicator=no",
"-Dwwan=false",
"-Dselinux=false",
"-Dteam=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/network-manager-applet.git",
"branch" : "main"
}
]
},
{
"name" : "NetworkManager-openvpn",
"config-opts" : [
"--with-gnome",
"--with-gtk4"
],
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.gnome.org/GNOME/NetworkManager-openvpn.git"
}
]
},
{
"name" : "NetworkManager-vpnc",
"config-opts" : [
"--with-gnome",
"--with-gtk4"
],
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.gnome.org/GNOME/NetworkManager-vpnc.git"
}
]
},
{
"name" : "ModemManager",
"buildsystem" : "meson",
"config-opts" : [
"-Dintrospection=false",
"-Dbash_completion=false",
"-Dudevdir=/app/lib/udev",
"-Dsystemdsystemunitdir=/app/lib/systemd/system",
"-Ddbus_policy_dir=/app/etc/dbus-1/system.d",
"-Dsystemd_journal=false",
"-Dtests=false",
"-Dman=false",
"-Dfuzzer=false",
"-Dexamples=false",
"-Dmbim=false",
"-Dplugin_dell=disabled",
"-Dplugin_foxconn=disabled",
"-Dplugin_fibocom=disabled",
"-Dqmi=false",
"-Dqrtr=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git",
"branch" : "main"
}
]
},
{
"name" : "gnome-settings-daemon",
"buildsystem" : "meson",
"config-opts" : [
"-Dudev_dir=/app/lib/udev",
"-Dsystemd=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/gnome-settings-daemon.git"
}
],
"modules": [
{
"name": "libcanberra",
"sources": [
{
"type": "archive",
"url": "http://0pointer.de/lennart/projects/libcanberra/libcanberra-0.30.tar.xz",
"sha256": "c2b671e67e0c288a69fc33dc1b6f1b534d07882c2aceed37004bf48c601afa72"
}
],
"config-opts": [
"--disable-alsa",
"--disable-null",
"--disable-oss"
]
}
]
},
{
"name" : "gsound",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/gsound.git"
}
]
},
{
"name" : "gnome-bluetooth",
"buildsystem" : "meson",
"config-opts" : [
"-Dintrospection=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/gnome-bluetooth.git"
}
]
},
{
"name" : "openldap",
"buildsystem" : "autotools",
"config-opts" : [
"--disable-slapd"
],
"sources" : [
{
"type": "archive",
"url": "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.8.tgz",
"sha256": "48969323e94e3be3b03c6a132942dcba7ef8d545f2ad35401709019f696c3c4e"
}
]
},
{
"name" : "libtirpc",
"buildsystem" : "autotools",
"sources" : [
{
"type" : "archive",
"url" : "http://downloads.sourceforge.net/sourceforge/libtirpc/libtirpc-1.2.6.tar.bz2",
"sha256" : "4278e9a5181d5af9cd7885322fdecebc444f9a3da87c526e7d47f7a12a37d1cc"
}
]
},
{
"name" : "rpcsvc-proto",
"buildsystem" : "autotools",
"sources" : [
{
"type" : "archive",
"url" : "https://github.com/thkukuk/rpcsvc-proto/archive/v1.4.tar.gz",
"sha256" : "eaa227686b84e8ae20181d2119c0ccac29d82371441844008019a134dafe3613"
}
]
},
{
"name" : "lmdb",
"buildsystem" : "simple",
"build-commands" : [
"cd libraries/liblmdb/ && make prefix=/app install"
],
"sources" : [
{
"type" : "archive",
"url" : "https://github.com/LMDB/lmdb/archive/LMDB_0.9.23.tar.gz",
"sha256" : "abf42e91f046787ed642d9eb21812a5c473f3ba5854124484d16eadbe0aa9c81"
}
]
},
{
"name": "yapp-driver",
"buildsystem": "simple",
"build-commands": [
"perl Makefile.PL",
"make install -j1"
],
"build-options" : {
"env" : {
"PERL5LIB": "/app/lib/perl5/",
"PERL_MM_OPT": "INSTALL_BASE=/app"
}
},
"cleanup": [ "*" ],
"sources": [
{
"type": "archive",
"url": "https://cpan.metacpan.org/authors/id/W/WB/WBRASWELL/Parse-Yapp-1.21.tar.gz",
"sha256": "3810e998308fba2e0f4f26043035032b027ce51ce5c8a52a8b8e340ca65f13e5"
}
]
},
{
"name" : "samba",
"buildsystem" : "autotools",
"config-opts" : [
"--without-json",
"--without-ad-dc",
"--without-pam"
],
"build-options" : {
"env" : {
"PERL5LIB": "/app/lib/perl5/",
"PERL_MM_OPT": "INSTALL_BASE=/app"
}
},
"sources" : [
{
"type" : "archive",
"url" : "https://download.samba.org/pub/samba/stable/samba-4.20.2.tar.gz",
"sha256" : "f969ffed58ccf3e85cbbcc0e33a1726d025c2b40f42a653b1125b82b92d2e0e5"
}
]
},
{
"name" : "libgtop2",
"buildsystem" : "autotools",
"config-opts" : [
"--disable-introspection"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/libgtop.git"
}
]
},
{
"name" : "udisks",
"buildsystem" : "autotools",
"config-opts" : [
"--disable-introspection",
"--disable-lvm2",
"--enable-daemon=no",
"--with-systemdsystemunitdir=/app/lib/systemd/system",
"--with-tmpfilesdir=/app/lib/tmpfiles.d"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/storaged-project/udisks.git"
}
]
},
{
"name": "python3-jinja2",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"jinja2\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz",
"sha256": "7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl",
"sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
}
]
},
{
"name": "python3-attrs",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"attrs\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/f0/eb/fcb708c7bf5056045e9e98f62b93bd7467eb718b0202e7698eb11d66416c/attrs-23.1.0-py3-none-any.whl",
"sha256": "1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"
}
]
},
{
"name" : "libei",
"buildsystem" : "meson",
"config-opts" : [
"-Dliboeffis=disabled",
"-Dtests=disabled"
],
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://gitlab.freedesktop.org/libinput/libei.git"
}
]
},
{
"name": "python3-docutils",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"docutils\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl",
"sha256": "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"
}
]
},
{
"name" : "mutter",
"buildsystem" : "meson",
"config-opts" : [
"-Dgles2=false",
"-Dglx=false",
"-Dxwayland=false",
"-Dwayland=false",
"-Dlogind=false",
"-Dbash_completion=false",
"-Dnative_backend=false",
"-Dremote_desktop=false",
"-Degl_device=false",
"-Dudev=false",
"-Dstartup_notification=false",
"-Dsm=false",
"-Dintrospection=true",
"-Ddocs=false",
"-Dtests=disabled",
"-Dprofiler=false"
],
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://gitlab.gnome.org/gnome/mutter.git"
}
],
"modules": [
{
"name" : "libdisplay-info",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://gitlab.freedesktop.org/emersion/libdisplay-info.git"
}
],
"modules": [
{
"name" : "hwdata",
"config-opts" : [
"--prefix=/app",
"--datadir=/app/share",
"--disable-blacklist"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/vcrhonek/hwdata.git"
}
]
}
]
}
]
},
{
"name" : "libical",
"buildsystem" : "cmake",
"config-opts" : [
"-DICAL_BUILD_DOCS=false",
"-DICAL_GLIB=true",
"-DLIBICAL_BUILD_TESTING=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://github.com/libical/libical.git"
}
]
},
{
"name" : "evolution-data-server",
"buildsystem" : "cmake",
"config-opts" : [
"-DWITH_LIBDB=OFF"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/evolution-data-server.git"
}
]
},
{
"name" : "libstartup-notification",
"buildsystem" : "autotools",
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.freedesktop.org/xdg/startup-notification.git"
}
]
},
{
"name" : "sassc",
"buildsystem" : "simple",
"sources" : [
{
"type" : "git",
"url" : "https://github.com/sass/libsass"
},
{
"type" : "git",
"url" : "https://github.com/sass/sassc.git",
"dest" : "sassc"
}
],
"build-commands" : [
"make -C sassc",
"PREFIX=/app make -C sassc install"
]
},
{
"name" : "cogl",
"buildsystem" : "autotools",
"sources" : [
{
"type" : "git",
"tag" : "cogl-1.22",
"url" : "https://gitlab.gnome.org/Archive/cogl.git"
}
]
},
{
"name" : "clutter",
"buildsystem" : "meson",
"config-opts" : [
"-Dbackends='system'",
"-Ddrivers='all'",
"-Dbuild_tests=false",
"-Dbuild_examples=false",
"-Ddocumentation=false",
"-Dintrospection=true",
"-Dpixbuf_tests=false"
],
"sources" : [
{
"type" : "git",
"url" : "https://gitlab.gnome.org/GNOME/clutter.git"
}
]
},
{
"name" : "gnome-shell",
"buildsystem" : "meson",
"config-opts" : [
"-Dextensions_tool=false",
"-Dextensions_app=false",
"-Dgtk_doc=false",
"-Dman=false",
"-Dtests=false",
"-Dnetworkmanager=false",
"-Dportal_helper=false",
"-Dcamera_monitor=false",
"-Dsystemd=false"
],
"sources" : [
{
"type" : "git",
"branch" : "main",
"url" : "https://gitlab.gnome.org/GNOME/gnome-shell.git"
}
]
},
{
"name" : "gnome-backgrounds",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.gnome.org/GNOME/gnome-backgrounds.git"
}
]
},
{
"name" : "tecla",
"buildsystem" : "meson",
"sources" : [
{
"type" : "git",
"branch": "main",
"url" : "https://gitlab.gnome.org/GNOME/tecla.git"
}
]
},
{
"name" : "gnome-control-center",
"buildsystem" : "meson",
"sources" : [
{
"type" : "dir",
"path" : "../../"
}
],
"config-opts" : [
"-Dsnap=false",
"-Dtests=false",
"-Dprofile=development"
]
}
]
}

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
import sys
def usage():
print('Usage:')
print('find_xdg_file.py FILENAME')
print('')
print('Looks for FILENAME in the XDG data directories and returns if path if found')
if len(sys.argv) != 2:
usage()
sys.exit(1)
filename = sys.argv[1]
data_home = os.getenv('XDG_DATA_HOME')
if not data_home or data_home == '':
data_home = os.path.join(os.path.expanduser("~"), "local", "share")
data_dirs_str = os.getenv('XDG_DATA_DIRS')
if not data_dirs_str or data_dirs_str == '':
data_dirs_str = '/usr/local/share/:/usr/share/'
dirs = []
dirs += [ data_home ]
for _dir in data_dirs_str.split(':'):
dirs += [ _dir ]
for _dir in dirs:
full_path = os.path.join(_dir, filename)
if os.path.exists(full_path):
print(full_path)
sys.exit(0)
print(f"'{filename}' not found in XDG data directories")
sys.exit(1)

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="64" x2="64" y1="262.5" y2="232">
<stop offset="0" stop-color="#9a9996"/>
<stop offset="1" stop-color="#77767b"/>
</linearGradient>
<radialGradient id="b" cx="-243.066583" cy="-74.599978" gradientUnits="userSpaceOnUse" r="28.266656">
<stop offset="0" stop-color="#d5d3cf"/>
<stop offset="1" stop-color="#949390"/>
</radialGradient>
<linearGradient id="c" gradientUnits="userSpaceOnUse" x1="203.999985" x2="267.999985" y1="-64" y2="-64">
<stop offset="0" stop-color="#d5d3cf"/>
<stop offset="1" stop-color="#f6f5f4"/>
</linearGradient>
<clipPath id="d">
<rect height="128" width="128"/>
</clipPath>
<clipPath id="e">
<rect height="128" width="128"/>
</clipPath>
<filter id="f" height="100%" width="100%" x="0%" y="0%">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="g">
<g clip-path="url(#e)" filter="url(#f)">
<g clip-path="url(#d)">
<path d="m 667 292 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(1 0 0 1 -600 -170)"/>
<path d="m 661 180 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(1 0 0 1 -600 -170)"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 667 292 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(1 0 0 1 -600 -172)"/>
<path d="m 661 180 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(1 0 0 1 -600 -172)"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
<path d="m 100 238 c 0 19.882812 -16.117188 36 -36 36 s -36 -16.117188 -36 -36 s 16.117188 -36 36 -36 s 36 16.117188 36 36 z m 0 0" fill="none" stroke="url(#a)" stroke-width="24" transform="matrix(1 0 0 1 0 -172)"/>
<path d="m 102 236 c 0 20.988281 -17.011719 38 -38 38 s -38 -17.011719 -38 -38 s 17.011719 -38 38 -38 s 38 17.011719 38 38 z m 0 0" fill="none" stroke="#f6f5f4" stroke-width="20" transform="matrix(1 0 0 1 0 -172)"/>
<path d="m -209.5 -64 c 0 14.636719 -11.863281 26.5 -26.5 26.5 s -26.5 -11.863281 -26.5 -26.5 s 11.863281 -26.5 26.5 -26.5 s 26.5 11.863281 26.5 26.5 z m 0 0" fill="none" stroke="url(#b)" stroke-linecap="square" stroke-linejoin="round" stroke-width="5" transform="matrix(0 -1 -1 0 0 -172)"/>
<path d="m 268 -64 c 0 17.671875 -14.328125 32 -32 32 s -32 -14.328125 -32 -32 s 14.328125 -32 32 -32 s 32 14.328125 32 32 z m 0 0" fill="none" stroke="url(#c)" stroke-linecap="square" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 0 -172)"/>
</g>
</g>
</mask>
<mask id="h">
<g filter="url(#f)">
<rect fill-opacity="0.8" height="128" width="128"/>
</g>
</mask>
<linearGradient id="i" gradientTransform="matrix(0 0.37 -0.98462 0 295.38501 -30.360001)" gradientUnits="userSpaceOnUse" x1="300" x2="428" y1="235" y2="235">
<stop offset="0" stop-color="#f9f06b"/>
<stop offset="1" stop-color="#f5c211"/>
</linearGradient>
<clipPath id="j">
<rect height="128" width="128"/>
</clipPath>
<clipPath id="k">
<rect height="128" width="128"/>
</clipPath>
<g stroke-linecap="round" stroke-linejoin="round" stroke-width="8">
<path d="m 67 122 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996"/>
<path d="m 61 10 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 67 120 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4"/>
<path d="m 61 8 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
</g>
<path d="m 100 238 c 0 19.882812 -16.117188 36 -36 36 s -36 -16.117188 -36 -36 s 16.117188 -36 36 -36 s 36 16.117188 36 36 z m 0 0" fill="none" stroke="url(#a)" stroke-width="24" transform="matrix(1 0 0 1 0 -172)"/>
<path d="m 102 64 c 0 20.988281 -17.011719 38 -38 38 s -38 -17.011719 -38 -38 s 17.011719 -38 38 -38 s 38 17.011719 38 38 z m 0 0" fill="none" stroke="#f6f5f4" stroke-width="20"/>
<path d="m -209.5 -64 c 0 14.636719 -11.863281 26.5 -26.5 26.5 s -26.5 -11.863281 -26.5 -26.5 s 11.863281 -26.5 26.5 -26.5 s 26.5 11.863281 26.5 26.5 z m 0 0" fill="none" stroke="url(#b)" stroke-linecap="square" stroke-linejoin="round" stroke-width="5" transform="matrix(0 -1 -1 0 0 -172)"/>
<path d="m 268 -64 c 0 17.671875 -14.328125 32 -32 32 s -32 -14.328125 -32 -32 s 14.328125 -32 32 -32 s 32 14.328125 32 32 z m 0 0" fill="none" stroke="url(#c)" stroke-linecap="square" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 0 -172)"/>
<g clip-path="url(#k)" mask="url(#g)">
<g clip-path="url(#j)" mask="url(#h)">
<path d="m 128 80.640625 v 47.359375 h -128 v -47.359375 z m 0 0" fill="url(#i)"/>
<path d="m 13.308594 80.640625 l 47.355468 47.359375 h 21.214844 l -47.359375 -47.359375 z m 42.421875 0 l 47.363281 47.359375 h 21.214844 l -47.363282 -47.359375 z m 42.429687 0 l 29.839844 29.839844 v -21.210938 l -8.628906 -8.628906 z m -98.160156 7.90625 v 21.214844 l 18.238281 18.238281 h 21.214844 z m 0 0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="64" x2="64" y1="262.5" y2="232">
<stop offset="0" stop-color="#9a9996"/>
<stop offset="1" stop-color="#77767b"/>
</linearGradient>
<radialGradient id="b" cx="-243.066583" cy="-74.599978" gradientUnits="userSpaceOnUse" r="28.266656">
<stop offset="0" stop-color="#d5d3cf"/>
<stop offset="1" stop-color="#949390"/>
</radialGradient>
<linearGradient id="c" gradientUnits="userSpaceOnUse" x1="203.999985" x2="267.999985" y1="-64" y2="-64">
<stop offset="0" stop-color="#d5d3cf"/>
<stop offset="1" stop-color="#f6f5f4"/>
</linearGradient>
<g stroke-width="8">
<path d="m 67 122 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m 61 10 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 1 -1 0 299.999908 -598.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -391.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -61.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -470.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#9a9996" stroke="#9a9996" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -627.040914)"/>
<path d="m 67 120 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m 61 8 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m 667.000092 291.999908 h -6 l -2 -16 h 10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 661.000092 179.999908 h 6 l 2 16 h -10 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 1 -1 0 299.999908 -600.000092)"/>
<path d="m 666.999051 291.998922 l -5.999272 -0.00042 l -2.001544 -15.998026 l 9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 661.001107 180.000804 l 5.999273 0.000419 l 2.001544 15.998026 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.5 0.866025 -0.866025 -0.5 600.381916 -393.041005)"/>
<path d="m 667.000347 291.998468 l -5.999273 0.00042 l -2.002482 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 660.999745 180.00119 l 5.999273 -0.00042 l 2.002481 16.00035 l -10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-0.866025 0.5 -0.5 -0.866025 757.040822 -63.618176)"/>
<path d="m 666.999018 291.999047 l -5.999273 -0.00042 l -2.001544 -15.998026 l 10.003298 -0.001904 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 661.001074 180.000929 l 5.999273 0.00042 l 2.001543 15.998025 l -10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.866025 0.5 -0.5 0.866025 -393.040914 -472.382008)"/>
<path d="m 667.00038 291.998593 l -5.999273 0.00042 l -2.002481 -16.00035 l 10.003298 0.001905 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
<path d="m 660.999779 180.001315 l 5.999272 -0.00042 l 1.999099 15.998397 l -9.999915 0.000049 z m 0 0" fill="#f6f5f4" stroke="#f6f5f4" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0.5 0.866025 -0.866025 0.5 -63.618084 -629.040914)"/>
</g>
<path d="m 100 238 c 0 19.882812 -16.117188 36 -36 36 s -36 -16.117188 -36 -36 s 16.117188 -36 36 -36 s 36 16.117188 36 36 z m 0 0" fill="none" stroke="url(#a)" stroke-width="24" transform="matrix(1 0 0 1 0 -172)"/>
<path d="m 102 64 c 0 20.988281 -17.011719 38 -38 38 s -38 -17.011719 -38 -38 s 17.011719 -38 38 -38 s 38 17.011719 38 38 z m 0 0" fill="none" stroke="#f6f5f4" stroke-width="20"/>
<path d="m -209.5 -64 c 0 14.636719 -11.863281 26.5 -26.5 26.5 s -26.5 -11.863281 -26.5 -26.5 s 11.863281 -26.5 26.5 -26.5 s 26.5 11.863281 26.5 26.5 z m 0 0" fill="none" stroke="url(#b)" stroke-linecap="square" stroke-linejoin="round" stroke-width="5" transform="matrix(0 -1 -1 0 0 -172)"/>
<path d="m 268 -64 c 0 17.671875 -14.328125 32 -32 32 s -32 -14.328125 -32 -32 s 14.328125 -32 32 -32 s 32 14.328125 32 32 z m 0 0" fill="none" stroke="url(#c)" stroke-linecap="square" stroke-linejoin="round" stroke-width="8" transform="matrix(0 1 -1 0 0 -172)"/>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="m 7.480469 0.015625 h 1.066406 c 0.589844 0 1.066406 0.476563 1.066406 1.066406 v 1.066407 c 0 0.589843 -0.476562 1.066406 -1.066406 1.066406 h -1.066406 c -0.589844 0 -1.066407 -0.476563 -1.066407 -1.066406 v -1.066407 c 0 -0.589843 0.476563 -1.066406 1.066407 -1.066406 z m 0 12.796875 h 1.066406 c 0.589844 0 1.066406 0.476562 1.066406 1.070312 v 1.0625 c 0 0.59375 -0.476562 1.070313 -1.066406 1.070313 h -1.066406 c -0.589844 0 -1.066407 -0.476563 -1.066407 -1.070313 v -1.0625 c 0 -0.59375 0.476563 -1.070312 1.066407 -1.070312 z m -5.5 -10.078125 l 0.753906 -0.753906 c 0.199219 -0.199219 0.46875 -0.3125 0.753906 -0.3125 c 0.28125 0 0.554688 0.113281 0.753907 0.3125 l 0.753906 0.753906 c 0.417968 0.417969 0.417968 1.089844 0 1.507813 l -0.753906 0.753906 c -0.199219 0.203125 -0.472657 0.316406 -0.753907 0.316406 c -0.285156 0 -0.554687 -0.113281 -0.753906 -0.316406 l -0.753906 -0.753906 c -0.203125 -0.199219 -0.316407 -0.46875 -0.316407 -0.753907 c 0 -0.28125 0.113282 -0.554687 0.316407 -0.753906 z m 9.050781 9.050781 l 0.753906 -0.753906 c 0.199219 -0.199219 0.472656 -0.3125 0.753906 -0.3125 c 0.285157 0 0.554688 0.113281 0.753907 0.3125 l 0.753906 0.753906 c 0.417969 0.417969 0.417969 1.089844 0 1.507813 l -0.753906 0.753906 c -0.199219 0.203125 -0.46875 0.316406 -0.753907 0.316406 c -0.28125 0 -0.554687 -0.113281 -0.753906 -0.316406 l -0.753906 -0.753906 c -0.203125 -0.199219 -0.3125 -0.46875 -0.3125 -0.753907 c 0 -0.28125 0.109375 -0.554687 0.3125 -0.753906 z m -11.015625 -3.238281 v -1.066406 c 0 -0.589844 0.472656 -1.066407 1.066406 -1.066407 h 1.066407 c 0.589843 0 1.066406 0.476563 1.066406 1.066407 v 1.066406 c 0 0.589844 -0.476563 1.066406 -1.066406 1.066406 h -1.066407 c -0.59375 0 -1.066406 -0.476562 -1.066406 -1.066406 z m 12.796875 0 v -1.066406 c 0 -0.589844 0.476562 -1.066407 1.066406 -1.066407 h 1.066406 c 0.59375 0 1.070313 0.476563 1.070313 1.066407 v 1.066406 c 0 0.589844 -0.476563 1.066406 -1.070313 1.066406 h -1.066406 c -0.589844 0 -1.066406 -0.476562 -1.066406 -1.066406 z m -10.078125 5.5 l -0.753906 -0.753906 c -0.203125 -0.199219 -0.316407 -0.46875 -0.316407 -0.753907 c 0 -0.28125 0.113282 -0.554687 0.316407 -0.753906 l 0.753906 -0.753906 c 0.199219 -0.199219 0.46875 -0.3125 0.753906 -0.3125 c 0.28125 0 0.554688 0.113281 0.753907 0.3125 l 0.753906 0.753906 c 0.417968 0.417969 0.417968 1.089844 0 1.507813 l -0.753906 0.753906 c -0.199219 0.203125 -0.472657 0.316406 -0.753907 0.316406 c -0.285156 0 -0.554687 -0.113281 -0.753906 -0.316406 z m 9.050781 -9.050781 l -0.753906 -0.753906 c -0.203125 -0.199219 -0.3125 -0.46875 -0.3125 -0.753907 c 0 -0.28125 0.109375 -0.554687 0.3125 -0.753906 l 0.753906 -0.753906 c 0.199219 -0.199219 0.472656 -0.3125 0.753906 -0.3125 c 0.285157 0 0.554688 0.113281 0.753907 0.3125 l 0.753906 0.753906 c 0.417969 0.417969 0.417969 1.089844 0 1.507813 l -0.753906 0.753906 c -0.199219 0.203125 -0.46875 0.316406 -0.753907 0.316406 c -0.28125 0 -0.554687 -0.113281 -0.753906 -0.316406 z m 0 0" fill="#241f31"/>
<path d="m 8.015625 1.515625 c -3.574219 0 -6.5 2.925781 -6.5 6.5 c 0 3.570313 2.925781 6.5 6.5 6.5 c 3.570313 0 6.5 -2.929687 6.5 -6.5 c 0 -3.574219 -2.929687 -6.5 -6.5 -6.5 z m 0 3 c 1.949219 0 3.5 1.546875 3.5 3.5 c 0 1.949219 -1.550781 3.5 -3.5 3.5 c -1.953125 0 -3.5 -1.550781 -3.5 -3.5 c 0 -1.953125 1.546875 -3.5 3.5 -3.5 z m 0 0" fill="#241f31"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

4
data/icons/meson.build Normal file
View file

@ -0,0 +1,4 @@
install_subdir(
'hicolor',
install_dir : control_center_icondir
)

View file

@ -0,0 +1,28 @@
# Building GNOME Settings as a Flatpak in GNOME Builder
An alternative way of building GNOME Settings is using [GNOME Builder](https://flathub.org/apps/org.gnome.Builder)'s Flatpak support. This allows for easily cloning the repository and automatically handling the build of dependencies.
> The Flatpak container sandbox prevents GNOME Settings from performing a few of its functions, so this method is only recommended for superficial changes such as updating strings, UI, and style. If you face issues with the Settings functionality while running it in Builder, [considering building GNOME settings in a Toolbx instead](https://gitlab.gnome.org/GNOME/gnome-control-center/-/wikis/build/Building-GNOME-Settings-in-a-Toolbx).
Follow the steps described at https://welcome.gnome.org/en-GB/app/Settings/#getting-the-app-to-build
## Known limitations in the Settings Flatpak
- Apps Settings
- The *Default Apps* settings won't get populated (your apps aren't visible inside the Flatpak sandbox)
- App list won't show apps that aren't part of the sandbox (only utilities that are part of the flatpak runtime/sdk)
- Notification Settings
- installed apps aren't visible inside the flatpak sandbox
- Search Settings
- The [shell search providers](https://developer.gnome.org/documentation/tutorials/search-provider.html) aren't visible from within the Flatpak sandbox
- Keyboard Settings
- Input sources list appears empty
- Printers Settings
- There's no way to unlock the panel (it is not possible to use polkit within the flatpak container)
- System > Users Settings
- There's no way to Unlock the panel (it is not possible to use polkit within the flatpak container)
- System -> Remote Desktop Settings
- The panel won't load because it requires the remote-session-helper to be installed in a prefix and with permissions that aren't available within the flatpak container
- System -> About Settings
- There's no way to change the "Device Name" because that requires escalating privileges with polkit (not possible within the flatpak container)
- The OS logo will fallback to the GNOME one (the distro provided one is not accessible from within the flatpak container)

View file

@ -0,0 +1,75 @@
# Building GNOME Settings in a Toolbx
[Toolbx](https://containertoolbx.org/) provides a Fedora-based development container that is ideal for developing GNOME Settings without risking messing up with your host operating system.
## Create a Toolbx with:
```bash
toolbox create --release <release-number>
```
(preferably choose the latest Fedora release)
After creation, you can enter your toolbx with
```bash
toolbox enter --release <release-number>
```
## Install development packages for GNOME Settings
To install the development dependencies in GNOME Settings you can use:
```bash
sudo dnf install -y gnome-desktop4-devel libgweather4-devel \
gnome-settings-daemon-devel libnma-gtk4-devel \
colord-gtk4-devel
sudo dnf builddep gnome-control-center
```
## Perform a local build
Inside the `gnome-control-center` repository you can prepare the build with:
```bash
meson setup _build
```
If all dependencies were satisfied, you should be able to build the project from inside the `_build` folder.
```bash
cd _build
ninja
```
At the first time you do this you should also run `sudo ninja install` so that gsettings and other resources are installed in system paths.
## Running
With that, you can run GNOME Settings from its executable:
```bash
./shell/gnome-control-center
```
## Tips
* If `meson _build` is still missing dependencies, you can check which dependencies we are using in our Fedora CI build for reference to what to install. See `FDO_DISTRIBUTION_PACKAGES` for the whole list of packages in https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/.gitlab-ci.yml#L88
## Additional packages for specific components
GNOME Settings should now open, but many components require additional packages inside the Toolbx to function properly.
- `rygel` and `gnome-user-share` for the Sharing Panel
- `gnome-shell` for Multitasking and the videos in Mouse
- `flatpak` for showing apps in Applications that are installed outside your Toolbx
- `gnome-backgrounds` and `fedora-workstation-backgrounds` to show some backgrounds in Appearance
- `NetworkManager-{vpnc,openvpn,openconnect,pptp,ssh}-gnome` for VPN functionality
- `yelp` and `gnome-user-docs` for showing help information (press F1)
To install all of these at once, you can use:
```bash
sudo dnf install -y rygel gnome-user-share gnome-shell flatpak \
gnome-backgrounds fedora-workstation-backgrounds \
NetworkManager-{vpnc,openvpn,openconnect,pptp,ssh}-gnome \
yelp gnome-user-docs
```

31
docs/CC_HOSTNAME.md Normal file
View file

@ -0,0 +1,31 @@
# CcHostname
[CcHostname](https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/panels/common/cc-hostname.h?ref_type=heads) is a singleton class useful for obtaining information about the host system throughout systemd-hostnamed.
`CcHostname` contains a proxy to the `org.freedesktop.hostname1` interface over DBus.
The main `CcHostname` methods are:
# `CcHostname.get_display_hostname`
Obtains the system "Pretty Hostname". See https://www.freedesktop.org/software/systemd/man/latest/hostnamectl.html#hostname%20%5BNAME%5D
# `CcHostname.get_display_hostname`
Obtains the system "Static Hostname".
# `CcHostname.set_hostname`
Allows for setting the system "pretty" and "static" hostnames (SetPrettyHostname and SetStaticHostname).
# `CcHostname.get_chassis_type`
Useful for when we want to condition the behaviour based on whether the device running Settings is a laptop, desktop, server, tablet, handset, etc.... See https://www.freedesktop.org/software/systemd/man/latest/hostnamectl.html#chassis%20%5BTYPE%5D
# `CcHostname.is_vm_chassis`
A convenient function that calls `CcHostname.get_chassis_type` internally to determine whether Settings is running in a virtual machine environment.
# `CcHostname.get_property`
Allows for queying other systemd-hostnamed properties. See `hostnamectl status` to list the available options.

54
docs/CC_PANEL.md Normal file
View file

@ -0,0 +1,54 @@
# CcPanel
[CcPanel](https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/shell/cc-panel.c?ref_type=heads) is the base class for each settings panel implementation.
`CcPanel` provides convenient navigation features for static and runtime subpages.
Static subpages are top-level setting pages such as "Screen Lock", "Location", "Thunderbolt" (in the Privacy settings) or "Date & Time", "Region & Language" (in the System settings).
Runtime subpages are pages that aren't likely part of the main execution hot path. These are pages usually hidden behind a non-toplevel row or an option.
The main conveniences of `CcPanel` are:
## Child packing
### Panel Subpages
`AdwNavigationPage` child pages of `CcPanel` defined with `<child type="subpage">`, will be added as pages to `CcPanel.navigation`. For example:
```xml
<template class="CcPrivacyPanel" parent="CcPanel">
<child type="subpage">
<object class="AdwNavigationPage">
```
This will result in:
* ### CcPanel (AdwNavigationPage)
- #### CcPanel.navigation (AdwNavigationView)
- ##### CcPanel.navigation.children +=
- ###### **child (AdwNavigationPage)**
### Other widgets
Widgets of other types will overwrite `CcPanel.child` and avoid the navigation mechanism entirely. **This is the case for all panels that don't have subpages**.
## API
## `CcPanel.add_static_subpage`
Allows for panels to create subpages on demand, rather than create at panel initialization.
This is useful for hub panels (such as System and Privacy), where we don't want to load all subpages at startup, given that users might just visit one.
## `CcPanel.push_subpage`
Allows for panel implementations to simply add an `AdwNavigationPage` to the top of the `CcPanel` navigation.
## `CcPanel.pop_visible_subpage`
Allows for panels to pop the current visible subpage from the navigation view.
## `CcPanel.get_visible_subpage`
Allows for panels to query for the visible subpage. This is useful when the panel implementation has custom behaviour depending on the page that is shown. For example, in the Privacy settings we check the current visible subpage so that we can determine what's the best documentation page to offer.

52
docs/CODEOWNERS Normal file
View file

@ -0,0 +1,52 @@
# These are the people responsible for Settings panels and domain-specific
# parts of the GNOME Settings codebase.
#
# If you open a merge request for files listed here, please add the following
# people to the list of reviewers.
# The syntax of this file is defined by GitLab:
# https://docs.gitlab.com/ee/user/project/code_owners.html
# Which, in turn, is similar to the .gitignore and .gitattributes files:
#
# - comments start with `#`
# - the first column contains paths and globs
# - the second column contains GitLab user names or email addresses,
# separated by spaces
#
# The last matching glob (rather than the union of *all* matching globs) gives
# the owners of a piece of code.
#
# If you want to be responsible for code reviews in specific sections of
# the GNOME Settings code base, add yourself here.
# General maintainers
* @felipeborges @robert_ancell
# Panels
## About
panels/info-overview/* @cyberphantom52
## Accessibility
panels/universal-access/* @pksadiq
## Appearance
panels/background/* @velsinki
## Apps
panels/applications/cc-default-apps-* @nishalkulkarni
panels/applications/cc-removable-media-settings* @nishalkulkarni
## Bluetooth
panels/bluetooth/* @hadess
## Printers
panels/printers/* @mkasik
## Sound
panels/sound/* @melix99
## Wacom
panels/wacom/* @carlosg
## WWAN (Cellular)
panels/wwan/* @pksadiq

184
docs/CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,184 @@
# Code of Conduct
GNOME Settings is a project developed based on GNOME Code of Conduct and GitHub's community
guidelines. You can read it below:
## Summary
GNOME creates software for a better world. We achieve this by behaving well towards
each other.
Therefore this document suggests what we consider ideal behaviour, so you know what
to expect when getting involved in GNOME. This is who we are and what we want to be.
There is no official enforcement of these principles, and this should not be interpreted
like a legal document.
## Advice
* **Be respectful and considerate**: Disagreement is no excuse for poor behaviour or personal
attacks. Remember that a community where people feel uncomfortable is not a productive one.
* **Be patient and generous**: If someone asks for help it is because they need it. Do politely
suggest specific documentation or more appropriate venues where appropriate, but avoid
aggressive or vague responses such as "RTFM".
* **Assume people mean well**: Remember that decisions are often a difficult choice between
competing priorities. If you disagree, please do so politely. If something seems outrageous,
check that you did not misinterpret it. Ask for clarification, but do not assume the worst.
* **Try to be concise**: Avoid repeating what has been said already. Making a conversation larger
makes it difficult to follow, and people often feel personally attacked if they receive multiple
messages telling them the same thing.
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Communication Guidelines
It is of ultimate importance to maintain a community in which everyone feels free to express
themselves, review, and comment on each others ideas, both technical and otherwise. Correspondingly,
an environment in which individuals are silenced, berated, or are otherwise afraid to speak up is
unlikely to foster fruitful dialog.
Everyone interacting with members of the community should always keep in mind the asymmetry of
communication: while your interaction with community members (and in particular, maintainers and
long-term contributors) may be singular and fleeting, these members generally interact with a high
volume of individuals each day. Before writing a comment, opening a new issue, or engaging as part
of any forum or IRC discussion, please take a moment to appreciate that fact.
While communicating, it is expected that all involved participants be respectful and civil at all
times and refrain from personal attacks.
### Communication Rules
The following behavior will not be tolerated on any occasion:
* **Threats of violence**: You may not threaten violence towards others or use the site to organize,
promote, or incite acts of real-world violence or terrorism. Think carefully about the words you
use, the images you post, and even the software you write, and how they may be interpreted by
others. Even if you mean something as a joke, it might not be received that way. If you think
that someone else might interpret the content you post as a threat or as promoting violence or
terrorism, stop. Don't post it. In extraordinary cases, we may report threats of violence to law
enforcement if we think there may be a genuine risk of physical harm or a threat to public safety.
* **Hate speech and discrimination**: While it is not forbidden to broach topics such as age, body
size, disability, ethnicity, gender identity and expression, level of experience, nationality,
personal appearance, race, religion, or sexual identity and orientation, we do not tolerate speech
that attacks a person or group of people on the basis of who they are. When approached in an
aggressive or insulting manner these (and other) sensitive topics can make others feel unwelcome,
or perhaps even unsafe. While there's always the potential for misunderstandings, we expect our
community members to remain respectful and civil when discussing sensitive topics.
* **Bullying and harassment**: We do not tolerate bullying, harassment, or any other means of
habitual badgering or intimidation targeted at a specific person or group of people. In general,
if your actions are unwanted and you cease to terminate this form of engagement, there is a good
chance that your behavior will be classified as bullying or harassment.
* **Impersonation**: You may not seek to mislead others as to your identity by copying another
person's avatar, posting content under their email address, using a similar username, or otherwise
posing as someone else. Impersonation and identity theft is a form of harassment.
* **Doxxing and invasion of privacy**: Don't post other people's personal information, such as phone
numbers, private email addresses, physical addresses, credit card numbers, Social Security/National
Identity numbers, or passwords. Depending on the context, we may consider such behavior to be an
invasion of privacy, with particularly egregious examples potentially escalating to the point of
legal action, such as when the released material presents a safety risk to the subject.
* **Obscene content**: In essence, do not post pornography, gore, or any other depiction of violence.
### General Advice
The following advice will help to increase the efficiency of communication with community members:
* Do not post "me too" comments. Use the GitLab reactions instead, e.g. “thumbs up” or “thumbs down”.
* Avoid adding priority, time, or relevance hints if you are not involved with the development of
the application. For example, `“This is an urgent issue”`, or `“This should be fixed now”`, or
even `“The majority of users need this feature”`.
* Do not use passive-aggressive communication tactics.
* When reporting technical problems with the application, such as misbehavior or crashes, focus on
sharing as many details as possible and avoid adding non-technical information to it.
An example of a **good** issue report:
```
GNOME Settings crashes when opening the Wi-Fi panel with 3+ Wi-Fi adapters
Steps to reproduce (assuming 3+ Wi-Fi adapters are present):
1. Open GNOME Settings
2. Select the Wi-Fi panel
3. Observe the crash
This does not happen with 2 or less adapters. Here is a backtrace of the
crash: backtrace.txt
```
In contrast, here is an example of a **bad** issue report:
```
GNOME Settings crashed while I was trying to connect to the internet. How can such
a thing happen and nobody notice? Did you not test it before releasing it?
This should be fixed as quick as possible!
```
* When asking for new features, try and add as much information as possible to justify its relevance,
why should it not be implemented as an auxiliary program, what problems it would solve, and offer
suggestions about how you think it should be implemented.
Example of a **good** feature request:
```
GNOME Settings needs to expose IPv6 options
As of now, the connection editor dialog does not allow editing various IPv6
options. This is relevant because without some of these options, it is not
possible to have a valid IPv6 configuration and, consequently, not have access
to various websites and services.
The list of missing configurations that are essential is:
* <Feature A>
* <Feature B>
Optionally, the following configurations can also be added:
* <Feature C>
* <Feature D>
Here is a quick sketch I have made showing how I think these options
should be exposed as a user interface: sketch.png.
```
Example of a **bad** feature request:
```
Merge GNOME Tweaks in GNOME Settings
The options in GNOME Tweaks are absolutely essential to the majority of us
users. Why was it not merged already? This is an urgent issue and should
have been addressed years ago. You should allocate all your resources on
merging those two applications.
```
### What happens if someone breaks these rules or guidelines?
Actions that may be taken in response to an abusive comment include but are not limited to:
* Content removal (when breaking any of the guidelines or rules)
* Content blocking (when breaking any of the guidelines or rules)
* Formal report to the Code of Conduct Committee (when breaking any of the rules)
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
[maintainers]: https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/docs/MAINTAINERS.md

290
docs/CODING_STYLE.md Normal file
View file

@ -0,0 +1,290 @@
# Style
GNOME Settings has a coding style based on GTK Coding Style, but with a few more
rules. Please read them carefully and, if in doubt, ask a maintainer for directions.
## General
The most important rule is: **see the surrounding code, and copy its style**.
That said, GNOME Settings assumes:
* 2 spaces of indentation
* 120 columns of line width
* Newline before `{`
Another rule that applies to function declarations is that all parameters are
aligned by the last '*'. There are plenty of examples below.
## Comments
Comment blocks should be formatted as following:
```c
/* Single line comment */
/* Multiline comments start at the first line of the comment block,
* but have the closing slash a line after. Every line starts with
* an asterisk that is aligned with every the rest of the block.
*/
```
## Conditionals
Conditionals should either be all in one line, or one per line. Newlines inside
conditionals are aligned by the last parenthesis.
Some examples below:
```c
// Single line if
if (a || b || (c && d))
return;
// Multiline if with nested parenthesis
if (long_boolean_variable_used_in_this_condition_a ||
long_boolean_variable_used_in_this_condition_b ||
(long_boolean_variable_used_in_this_condition_c &&
long_boolean_variable_used_in_this_condition_d))
{
return;
}
// Another single line example with do {} while (...)
do
{
/* something */
}
while (a || b || (c && d));
```
## Structs and Enums
Structures and enums are formatted as following:
```c
struct _FooBar
{
guint32 field_one;
gchar *text;
};
typedef struct
{
FooParent parent;
guint32 field_one;
gchar *text;
struct
{
CustomType *something;
guint something_else;
} inner_struct;
gboolean flag : 1;
} FooBar;
enum
{
FIRST,
SECOND,
LAST,
};
typedef enum
{
FOO_BAR_FIRST,
FOO_BAR_SECOND,
FOO_BAR_LAST,
} FooEnumBar;
```
## Header (.h) files
It is organized by the following structure:
1. GPL header
2. Local includes
3. System includes
4. `G_BEGIN_DECLS`
5. `#defines`
6. `G_DECLARE_{FINAL,DERIVABLE}_TYPE`
7. Public API
8. `G_END_DECLS`
The following style rules apply:
* The '*' and the type come together, without any spaces in between.
* Function names are aligned by the widest return value.
* Parenthesis after function name is aligned by the widest function name
* The last '*' in parameters are aligned by the widest parameter type
* No new line at the end of the file
As an example, this is how a header file should look like (extracted from
the `cc-object-storage.h` file):
```c
/* cc-object-storage.h
*
* Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* 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-object.h>
#include <gio/gio.h>
G_BEGIN_DECLS
/* Default storage keys */
#define CC_OBJECT_NMCLIENT "CcObjectStorage::nm-client"
#define CC_TYPE_OBJECT_STORAGE (cc_object_storage_get_type())
G_DECLARE_FINAL_TYPE (CcObjectStorage, cc_object_storage, CC, OBJECT_STORAGE, GObject)
gboolean cc_object_storage_has_object (const gchar *key);
void cc_object_storage_add_object (const gchar *key,
gpointer object);
gpointer cc_object_storage_get_object (const gchar *key);
gpointer cc_object_storage_create_dbus_proxy_sync (GBusType bus_type,
GDBusProxyFlags flags,
const gchar *name,
const gchar *path,
const gchar *interface,
GCancellable *cancellable,
GError **error);
void cc_object_storage_create_dbus_proxy (GBusType bus_type,
GDBusProxyFlags flags,
const gchar *name,
const gchar *path,
const gchar *interface,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
G_END_DECLS
```
## Source code
The source file keeps an order of methods. The order will be as following:
1. GPL header
2. Structures
3. Function prototypes
4. G_DEFINE_TYPE()
5. Enums
6. Static variables
7. Auxiliary methods
8. Callbacks
9. Interface implementations
10. Parent class overrides
11. class_init and init
12. Public API
### Structures
The structures must have the first pointer asterisk aligned one space after the
widest type name. For example:
```c
typedef struct
{
GBusType bus_type;
GDBusProxyFlags flags;
gchar *name;
gchar *path;
gchar *interface;
gboolean cached;
} TaskData;
```
### Function Prototypes
Function prototypes must be formatted just like in header files.
### Auxiliary Methods
Auxiliary method names must have a verb in the dictionary form, and should always
perform an action over something. They don't have the `cc_` prefix. For example:
```c
static void
execute_something_on_data (Foo *data,
Bar *bar)
{
/* ... */
}
```
### Callbacks
* Callbacks always have the `_cb` suffix
* Signal callbacks always have the `on_<object_name>` prefix
* Callback names must have the name of the signal in the past
For example:
```c
static void
on_foo_size_allocated_cb (GtkWidget *widget,
GtkAllocation *allocation,
gpointer user_data)
{
/* ... */
}
```
### Line Splitting
Line splitting works following the GTK code style, but legibility comes over above
all. If a function call looks unbalanced following the GTK style, it is fine to
slightly escape the rules.
For example, this feels extremelly unbalanced:
```c
foo_bar_do_somthing_sync (a,
1,
object,
data,
something
cancellable,
&error);
```
Notice the empty space before the arguments, and how empty and odd it looks. In
comparison, it will look better if written like this:
```c
foo_bar_do_somthing_sync (a, 1, object, data,
something
cancellable,
&error);
```
# Contributing guidelines
See CONTRIBUTIONS.md file for the contribution guidelines, and the Code of Conduct
that contributors are expected to follow.

97
docs/CONTRIBUTING.md Normal file
View file

@ -0,0 +1,97 @@
# Contributing
Thank you for considering contributing to Settings!
All code contributions are made using merge requests.
Please note we have a Code of Conduct, please follow it in all your interactions with the project.
## Creating Merge Requests
To open a merge request fork the Settings project, and then create a branch for your change.
When the change is ready, submit a merge request.
The following guidelines will help your change be successfully merged:
* Keep the change as small as possible. If you can split it into multiple merge requests please do
so.
* Use multiple commits. This makes is easier to review and helps to diagnose bugs in the future.
* Use clear commit messages (see below).
* Attach screenshots of the change.
* Link to relevant issues this change is related to.
* Always set the merge request to allow maintainer edits - this makes it easier
for a maintainer to make small improvements to land the change faster.
Please format commit messages as follows:
```
component: <summary>
A paragraph explaining the problem and its context.
Another one explaining how you solved that.
<link to issue(s) this change fixes>
```
## Bug Fixes
Changes that fix bugs include:
* Correcting code that crashes.
* Spelling mistakes in labels.
* Small layout issues (e.g. spacing).
* Use of deprecated APIs.
* Restructuring of code for improved safety.
Please include clear information in the commit message(s) and merge request that indicate what is
being fixed by the change.
These changes will be reviewed for correctness, and then merged once this is complete.
## User Experience Changes
Changes that impact the user experience of Settings require approval from the
[Design Team][design-team]. This includes:
* Addition or removal of features in existing panels.
* Changes to the layout of panels.
* New panels.
Please include before/after screenshots in the merge request to show what is being changed.
For one of these changes to be merged one of the following is required:
* The change is shown in an existing mockup done by the design team (linked to in the merge request
or issue).
* A comment from a design team member in the merge request or issue approving the change.
If a merge request is opened without the above the "Needs Design" label attached to it and will not
be merged until design approval is received.
Once design approval is obtained, the change will be reviewed for correctness, and then merged once
this is complete.
If design approval is not obtained, the merge request will be closed.
[design-team]: https://gitlab.gnome.org/Teams/Design
## Reviews
All changes require approval from one or more Settings maintainers.
Reviews are likely to ask for changes to be made, please respond to these as soon as you are able
and ask for clarification if the requests are not clear.
When the change is ready to land a maintainer will mark it as approved.
Then the change can be merged by either a maintainer or the submitter if they have suitable
permissions.
## Draft Merge Requests
Merge requests marked as draft will not be reviewed by Settings maintainers or merged.
When the change is ready for review please mark the merge request as ready.
## Inactive Merge Requests
If a merge request has comments from maintainers that have not been responded to within 4 weeks this
merge request is considered to be inactive and will be closed. The reporter may re-open it at a
later date if they respond to the comments.

62
docs/MAINTAINERS.md Normal file
View file

@ -0,0 +1,62 @@
This document describes how maintainership works on GNOME Settings. It is intended to be a guideline
for future reference.
The list of current maintainers can be found at the [gnome-control-center.doap][doap] DOAP file.
# General Rules
The purpose of the shared maintainership model in GNOME Settings is to avoid as much as possible
pushing unreviewed code in the main repository. Not only it is a good engineering practice, but it
also increases the code quality and reduces the number of bugs.
GNOME Settings has two types of maintainers:
* **General Maintainer**: take care of the whole codebase and of panels without a specific maintainer.
* **Panel Maintainer**: take care of a specific panel with a stronger authority over General
Maintainers.
## For Contributors
Panel Maintainers have a stronger authority over their panels than a General Maintainer. If you are
contributing to a specific panel, and that panel has a dedicate maintainer, they should be the main
point of contact.
In the rare case of Panel Maintainers not being responsive, it is allowed to contact General
Maintainers.
## For Maintainers
If you are a Panel Maintainer, your merge requests will be reviewed by General Maintainer. The
opposite is true as well - General Maintainers contributing to a specific panel will have their
merge requests reviewed by the Panel Maintainer of that panel.
If you are a General Maintainer contributing to an unmaintained panel, or to the main codebase, your
merge requests will be reviewed by another General Maintainer.
Avoid pushing commits without an explicit review, except in the following cases:
* The commit is a translation commit
* The commit is trivial
* The commit is urgent and no reviewers were available in time
When accepting a merge request and allowing the other maintainer to merge, avoid misunderstandings
by being explicit. Suggested acceptance phrase:
`I approve this merge request. You are allowed to merge it.`
### Urgency Commits
Urgency commits should never happen, but in case they're needed, they are defined by the following
criteria:
* On stable branches (or `main` right after a stable release)
* Symptoms:
* Always OR often reproducible; AND
* Crash; OR
* Data loss; OR
* System corruption
* Quickly followed by an emergency release (at most 2 days after the commit)
[doap]: https://gitlab.gnome.org/GNOME/gnome-control-center/blob/main/gnome-control-center.doap

7
docs/PRINTERS_TESTING.md Normal file
View file

@ -0,0 +1,7 @@
# Testing the Printers panel without a printer
The cups package in most distributions includes the lpadmin tool. With that you can create a "dummy" printer that redirects its output to /dev/null.
```bash
sudo lpadmin -p FakePrinter -E -v file:/dev/null
```

View file

@ -0,0 +1,9 @@
# Workaround: A crashing panel preventing Settings from starting up
GNOME Settings saves the last opened panel when it is closed and attempts to start on that same panel when starting up again. If the panel has issues, you won't be able to start GNOME Settings.
To workaround the issue you can reset the last-opened setting with:
```gsettings reset org.gnome.Settings last-panel```
Please, try to [obtain a stack trace](https://wiki.gnome.org/GettingInTouch/Bugzilla/GettingTraces/Details) of the crashing panel and [report an issue](https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/new) attaching it.

View file

@ -0,0 +1,6 @@
# Reordering the sidebar panel list
Since 46.alpha the order of panels in the sidebar as well as the position of the separators is defined in this array https://gitlab.gnome.org/GNOME/gnome-control-center/-/blob/main/shell/cc-panel-list.c#L350
* Use the string "separator" to introduce a separator between two rows
* A "separator" can't be the last row

View file

@ -0,0 +1,5 @@
# Mocking fingerprint devices
To work in the fingerprint enrollment and manager in the User accounts, one might have to simulate device events.
For that, use https://gitlab.freedesktop.org/3v1n0/fprintd-mock-runner

61
docs/WACOM_TESTING.md Normal file
View file

@ -0,0 +1,61 @@
# Testing Wacom panel without a tablet or pen
## Setup
The `hid-replay` utility that can replay device events, simulating pen and touch behavior. This utility provides pre-built binaries ready for use on its [releases](https://github.com/hidutils/hid-replay/releases/) page, you can grab the `hid-replay.zip` file and run it with:
```
unzip hid-replay.zip
chmod +x hid-replay
sudo ./hid-replay <path-to-recording>
```
The tool can also be obtained from its source code with:
```bash
git clone https://github.com/hidutils/hid-replay/
```
To compile it use the `cargo` command after installing the required dependencies:
```bash
dnf install llvm-devel clang-devel # on Fedora
apt install llvm-dev libclang-dev # on Debian/Ubuntu
cargo build
sudo CARGO_INSTALL_ROOT=/usr/local cargo install hid-replay
```
The last command will install `hid-replay` as `/usr/local/bin/hid-replay`.
Peter Hutterer kindly maintains a collection of Wacom device event recordings in a git repository that can be downloaded with:
```bash
git clone https://github.com/whot/wacom-recordings
```
## Playing device recordings
After setting up the environment, you can run the **hid-replay** tool (inside the hid-tools folder):
```bash
sudo hid-replay <path-to-recording>
```
(where \<path-to-recording\> is one of the `.hid` files in the **wacom-recordings** collection.
## Testing GNOME Settings Wacom panel
While the hid-replay tool runs in the background, you can open GNOME Settings -> Wacom and you will see the settings for the correspondent device of the recording.
For example:
```bash
sudo hid-replay wacom-recordings/Wacom\ Intuos\ Pro\ M/pen.pen-light-horizontal.hid
```
![image_2023-11-01_11-23-33](uploads/6ac48ebd50b20ee5437d9b587d6621f7/image_2023-11-01_11-23-33.png)
## Limitations
This setup currently doesn't work in a container (such as Toolbx) as it has insufficient permissions.
This was tested in Fedora Workstation 39.

15
docs/index.md Normal file
View file

@ -0,0 +1,15 @@
# GNOME Settings Developer Documentation
GNOME Settings, also known as gnome-control-center, is the central application for managing system and desktop configurations in the GNOME desktop environment.
This documentation is focused on developers, designers, and translators, contributing or interested in understanding the project internals.
## Guides/HowDoI
* [Building GNOME Settings with Toolbx](BUILDING_WITH_TOOLBX.md)
* [Building GNOME Settings with Flatpak/GNOME Builder](BUILDING_WITH_FLATPAK_BUILDER.md)
* [Testing the Printers panel without a printer](PRINTERS_TESTING.md)
* [Testing the Fingerprint settings without a fingerprint reader](USERS_FINGERPRINT_MOCKING.md)
* [Testing the Wacom settings without a tablet](WACOM_TESTING.md)
* [Reordering the panel list sidebar](SIDEBAR_PANEL_LIST.md)
* [Reset the last-visited panel setting](RESET_LAST_VISITED_PANEL.md)

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
xmlns:gt="https://www.gnu.org/s/gettext/ns/its/extensions/1.0"
version="2.0">
<its:translateRule selector="/KeyListEntries" translate="no"/>
<its:translateRule selector="/KeyListEntries/@name" translate="yes"/>
<its:translateRule selector="//KeyListEntry" translate="yes"/>
<its:translateRule selector="//KeyListEntry/@description" translate="yes"/>
<gt:contextRule selector="//KeyListEntry[@msgctxt]" contextPointer="@msgctxt"/>
<!-- Extracted strings are consumed by the library and are never
merged back; we don't want to escape special characters. -->
<gt:escapeRule selector="/KeyListEntries" escape="no"/>
</its:rules>

View file

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<locatingRules>
<locatingRule name="GnomeKeybindings" pattern="*.xml">
<documentRule localName="KeyListEntries" target="gnome-keybindings.its"/>
</locatingRule>
</locatingRules>

6
gettext/its/sounds.its Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
version="2.0">
<its:translateRule selector="/sounds" translate="no"/>
<its:translateRule selector="//name" translate="yes"/>
</its:rules>

6
gettext/its/sounds.loc Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<locatingRules>
<locatingRule name="sounds" pattern="*.xml">
<documentRule localName="sounds" target="sounds.its"/>
</locatingRule>
</locatingRules>

View file

@ -0,0 +1,424 @@
/* cc-global-shortcut-dialog.c
*
* Copyright (C) 2010 Intel, Inc
* Copyright (C) 2016 Endless, Inc
* Copyright (C) 2020 System76, Inc.
* Copyright (C) 2022 Purism SPC
* Copyright © 2024 GNOME Foundation Inc.
* 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/>.
*
* Author: Thomas Wood <thomas.wood@intel.com>
* Georges Basile Stavracas Neto <gbsneto@gnome.org>
* Ian Douglas Scott <idscott@system76.com>
* Mohammed Sadiq <sadiq@sadiqpk.org>
* Dorota Czaplejewicz <gnome@dorotac.eu>
* Jonas Ådahl <jadahl@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <adwaita.h>
#include <config.h>
#include <glib/gi18n.h>
#include <gxdp.h>
#include "panels/keyboard/cc-keyboard-resources.h"
#include "cc-global-shortcut-dialog.h"
#include "cc-keyboard-shortcut-group.h"
#include "cc-util.h"
enum
{
DONE,
N_SIGNALS
};
static guint signals[N_SIGNALS];
enum
{
PROP_0,
PROP_APP_ID,
PROP_PARENT_WINDOW_HANDLE,
PROP_SHORTCUTS,
N_PROPS,
};
static GParamSpec *props[N_PROPS];
struct _CcGlobalShortcutDialog
{
AdwWindow parent_instance;
AdwPreferencesPage *shortcut_list;
GtkSizeGroup *accelerator_size_group;
GVariant *app_shortcuts;
/* Contains `CcKeyboardItem`s */
GListStore *shortcuts;
CcKeyboardManager *manager;
AdwPreferencesGroup *shortcuts_group;
char *app_id;
char *parent_window_handle;
GxdpExternalWindow *external_window;
gboolean has_new_shortcuts;
};
G_DEFINE_TYPE (CcGlobalShortcutDialog,
cc_global_shortcut_dialog,
ADW_TYPE_WINDOW)
static void
populate_shortcuts_model (CcGlobalShortcutDialog *self,
const char *section_id,
const char *section_title)
{
GtkWidget *shortcuts_group;
self->shortcuts = g_list_store_new (CC_TYPE_KEYBOARD_ITEM);
shortcuts_group =
cc_keyboard_shortcut_group_new (G_LIST_MODEL (self->shortcuts),
section_id, section_title,
self->manager,
self->accelerator_size_group);
self->shortcuts_group = ADW_PREFERENCES_GROUP (shortcuts_group);
}
static void
shortcut_added_cb (CcGlobalShortcutDialog *self,
CcKeyboardItem *item,
const char *section_id,
const char *section_title)
{
if (strcmp (section_id, self->app_id) == 0)
g_list_store_append (self->shortcuts, item);
}
static void
shortcuts_loaded_cb (CcGlobalShortcutDialog *self)
{
adw_preferences_page_add (self->shortcut_list, self->shortcuts_group);
}
static void
emit_done (CcGlobalShortcutDialog *self,
gboolean success)
{
g_autoptr(GVariant) response = NULL;
if (success)
{
cc_keyboard_manager_store_global_shortcuts (self->manager, self->app_id);
response = cc_keyboard_manager_get_global_shortcuts (self->manager,
self->app_id);
}
g_signal_emit (self, signals[DONE], 0, response);
}
static gboolean
close_request_cb (CcGlobalShortcutDialog *self)
{
emit_done (self, FALSE);
return GDK_EVENT_STOP;
}
static void
on_add_button_clicked_cb (CcGlobalShortcutDialog *self)
{
emit_done (self, TRUE);
}
static void
cc_global_shortcut_dialog_finalize (GObject *object)
{
CcGlobalShortcutDialog *self = CC_GLOBAL_SHORTCUT_DIALOG (object);
g_clear_object (&self->external_window);
g_clear_object (&self->manager);
g_clear_object (&self->shortcut_list);
g_clear_object (&self->accelerator_size_group);
g_clear_pointer (&self->app_id, g_free);
g_clear_pointer (&self->parent_window_handle, g_free);
g_clear_pointer (&self->app_shortcuts, g_variant_unref);
G_OBJECT_CLASS (cc_global_shortcut_dialog_parent_class)->finalize (object);
}
static void
cc_global_shortcut_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcGlobalShortcutDialog *self = CC_GLOBAL_SHORTCUT_DIALOG (object);
switch (prop_id)
{
case PROP_APP_ID:
self->app_id = g_value_dup_string (value);
break;
case PROP_PARENT_WINDOW_HANDLE:
self->parent_window_handle = g_value_dup_string (value);
break;
case PROP_SHORTCUTS:
self->app_shortcuts = g_value_dup_variant (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GVariant *
lookup_in_settings_variant (GVariant *settings,
const char *shortcut_id)
{
GVariantIter iter;
g_autofree char *key = NULL;
g_autoptr(GVariant) value = NULL;
g_variant_iter_init (&iter, settings);
while (g_variant_iter_next (&iter, "(s@a{sv})", &key, &value))
{
g_autofree char *shortcut = g_steal_pointer (&key);
g_autoptr(GVariant) config = g_steal_pointer (&value);
if (g_strcmp0 (shortcut_id, shortcut) == 0)
return g_steal_pointer (&config);
}
return NULL;
}
static GVariant *
app_shortcuts_to_settings_variant (GVariant *app_shortcuts,
GVariant *old_settings,
gboolean *has_new_shortcuts)
{
g_auto(GVariantBuilder) builder =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(sa{sv})"));
g_autoptr(GVariant) value = NULL;
g_autofree char *key = NULL;
GVariantIter iter;
g_variant_iter_init (&iter, app_shortcuts);
while (g_variant_iter_next (&iter, "(s@a{sv})", &key, &value))
{
g_autofree char *shortcut_id = g_steal_pointer (&key);
g_autoptr(GVariant) prefs = g_steal_pointer (&value);
g_autoptr(GVariant) setting = NULL;
setting = lookup_in_settings_variant (old_settings, shortcut_id);
if (setting)
{
/* Shortcut was configured previously */
g_variant_builder_add (&builder, "(s@a{sv})", shortcut_id, setting);
}
else
{
g_auto(GVariantBuilder) new_shortcut =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_auto(GVariantDict) prefs_dict = G_VARIANT_DICT_INIT (prefs);
g_autoptr(GVariant) description = NULL, preferred_trigger = NULL;
g_autoptr(GVariant) shortcuts = NULL;
/* Extract app preferences for new shortcut */
description =
g_variant_dict_lookup_value (&prefs_dict, "description",
G_VARIANT_TYPE_STRING);
if (!description)
continue;
g_variant_builder_add (&new_shortcut, "{sv}", "description",
g_variant_ref_sink (description));
preferred_trigger =
g_variant_dict_lookup_value (&prefs_dict, "preferred_trigger",
G_VARIANT_TYPE_STRING);
shortcuts = g_variant_new_array (G_VARIANT_TYPE_STRING,
preferred_trigger ? &preferred_trigger : NULL,
preferred_trigger ? 1 : 0);
g_variant_builder_add (&new_shortcut, "{sv}", "shortcuts",
g_variant_ref_sink (shortcuts));
g_variant_builder_add (&builder, "(s@a{sv})", shortcut_id,
g_variant_builder_end (&new_shortcut));
*has_new_shortcuts = TRUE;
}
}
return g_variant_builder_end (&builder);
}
static void
cc_global_shortcut_dialog_constructed (GObject *object)
{
CcGlobalShortcutDialog *self = CC_GLOBAL_SHORTCUT_DIALOG (object);
g_autoptr(GVariant) saved_shortcuts = NULL, shortcuts = NULL;
saved_shortcuts = cc_keyboard_manager_get_global_shortcuts (self->manager,
self->app_id);
shortcuts = app_shortcuts_to_settings_variant (self->app_shortcuts,
saved_shortcuts,
&self->has_new_shortcuts);
populate_shortcuts_model (self, self->app_id,
cc_util_app_id_to_display_name (self->app_id));
g_signal_connect_object (self->manager,
"shortcut-added",
G_CALLBACK (shortcut_added_cb),
self, G_CONNECT_SWAPPED);
/* Shortcuts can not get removed from this dialog,
* it's an offer, and offers don't change when you look away. */
g_signal_connect_object (self->manager,
"shortcuts-loaded",
G_CALLBACK (shortcuts_loaded_cb),
self, G_CONNECT_SWAPPED);
cc_keyboard_manager_load_global_shortcuts (self->manager,
self->app_id,
shortcuts);
G_OBJECT_CLASS (cc_global_shortcut_dialog_parent_class)->constructed (object);
}
static void
cc_global_shortcut_dialog_class_init (CcGlobalShortcutDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_global_shortcut_dialog_finalize;
object_class->set_property = cc_global_shortcut_dialog_set_property;
object_class->constructed = cc_global_shortcut_dialog_constructed;
props[PROP_APP_ID] =
g_param_spec_string ("app-id", NULL, NULL, NULL,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
props[PROP_PARENT_WINDOW_HANDLE] =
g_param_spec_string ("parent-window-handle", NULL, NULL, NULL,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
props[PROP_SHORTCUTS] =
g_param_spec_variant ("shortcuts", NULL, NULL,
G_VARIANT_TYPE ("a(sa{sv})"), NULL,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, props);
signals[DONE] =
g_signal_new ("done",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_VARIANT);
g_resources_register (cc_keyboard_get_resource ());
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/control-center/global-shortcuts-provider/"
"cc-global-shortcut-dialog.ui");
gtk_widget_class_bind_template_child (widget_class,
CcGlobalShortcutDialog,
shortcut_list);
gtk_widget_class_bind_template_child (widget_class,
CcGlobalShortcutDialog,
accelerator_size_group);
gtk_widget_class_bind_template_callback (widget_class,
on_add_button_clicked_cb);
gtk_widget_class_add_binding_action (widget_class,
GDK_KEY_Escape, 0,
"window.close", NULL);
}
static void
cc_global_shortcut_dialog_init (CcGlobalShortcutDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->manager = cc_keyboard_manager_new ();
g_signal_connect (self, "close-request",
G_CALLBACK (close_request_cb), NULL);
}
CcGlobalShortcutDialog *
cc_global_shortcut_dialog_new (const char *app_id,
const char *parent_window,
GVariant *shortcuts)
{
return g_object_new (CC_TYPE_GLOBAL_SHORTCUT_DIALOG,
"app-id", app_id,
"parent-window-handle", parent_window,
"shortcuts", shortcuts,
NULL);
}
void
cc_global_shortcut_dialog_present (CcGlobalShortcutDialog *self)
{
if (!self->has_new_shortcuts)
{
emit_done (self, TRUE);
return;
}
if (!gtk_widget_get_visible (GTK_WIDGET (self)))
{
self->external_window =
gxdp_external_window_new_from_handle (self->parent_window_handle);
if (self->external_window)
{
GtkNative *native;
GdkSurface *surface;
gtk_widget_realize (GTK_WIDGET (self));
native = gtk_widget_get_native (GTK_WIDGET (self));
surface = gtk_native_get_surface (native);
gxdp_external_window_set_parent_of (self->external_window,
surface);
}
gtk_window_present (GTK_WINDOW (self));
}
}

View file

@ -0,0 +1,44 @@
/* cc-global-shortcut-dialog.h
*
* Copyright (C) 2020 System76, 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: Ian Douglas Scott <idscott@system76.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "cc-keyboard-manager.h"
#include <adwaita.h>
#include <gio/gio.h>
G_BEGIN_DECLS
#define CC_TYPE_GLOBAL_SHORTCUT_DIALOG (cc_global_shortcut_dialog_get_type ())
G_DECLARE_FINAL_TYPE (CcGlobalShortcutDialog,
cc_global_shortcut_dialog,
CC, GLOBAL_SHORTCUT_DIALOG,
AdwWindow)
CcGlobalShortcutDialog *cc_global_shortcut_dialog_new (const char *app_id,
const char *parent_window,
GVariant *shortcuts);
void cc_global_shortcut_dialog_present (CcGlobalShortcutDialog *dialog);
G_END_DECLS

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcGlobalShortcutDialog" parent="AdwWindow">
<property name="modal">True</property>
<property name="default-width">480</property>
<property name="title" translatable="yes">Add Keyboard Shortcuts</property>
<child>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-start-title-buttons">False</property>
<property name="show-end-title-buttons">False</property>
<child type="start">
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">_Cancel</property>
<property name="use-underline">True</property>
<property name="valign">center</property>
<property name="action-name">window.close</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="add_button">
<property name="label" translatable="yes">_Add</property>
<property name="use-underline">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_add_button_clicked_cb" object="CcGlobalShortcutDialog" swapped="yes" />
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
<property name="content">
<object class="AdwPreferencesPage" id="shortcut_list">
<property name="description" translatable="yes">Added keyboard shortcuts will be available globally, and can be changed in the applications panel in Settings</property>
<property name="description-centered">True</property>
</object>
</property>
</object>
</child>
</template>
<object class="GtkSizeGroup" id="accelerator_size_group"/>
</interface>

View file

@ -0,0 +1,190 @@
/*
* Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
* Copyright © 2024 GNOME Foundation Inc.
*
* 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
*
* Contributor: Dorota Czaplejewicz
*/
#include <config.h>
#include <gio/gdesktopappinfo.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>
#include "cc-keyboard-manager.h"
#include <shell/cc-panel-loader.h>
#include <shell/cc-panel.h>
#include <shell/cc-shell-model.h>
#include "cc-global-shortcut-dialog.h"
#include "cc-global-shortcuts-provider.h"
#include "cc-util.h"
#include "control-center-global-shortcuts-provider.h"
struct _CcGlobalShortcutsProvider
{
GObject parent;
CcSettingsGlobalShortcutsProvider *skeleton;
GtkApplication *app;
GHashTable *dialogs;
};
typedef enum
{
MATCH_NONE,
MATCH_PREFIX,
MATCH_SUBSTRING
} PanelSearchMatch;
G_DEFINE_TYPE (CcGlobalShortcutsProvider,
cc_global_shortcuts_provider,
G_TYPE_OBJECT)
static void
handle_dialog_done (CcGlobalShortcutsProvider *self,
CcGlobalShortcutDialog *shortcut_dialog,
GVariant *response)
{
GDBusMethodInvocation *invocation;
invocation = g_hash_table_lookup (self->dialogs, shortcut_dialog);
if (response)
{
cc_settings_global_shortcuts_provider_complete_bind_shortcuts (self->skeleton,
invocation,
g_variant_ref_sink (response));
}
else
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Access denied");
}
}
static void
on_dialog_done (CcGlobalShortcutDialog *shortcut_dialog,
GVariant *response,
CcGlobalShortcutsProvider *self)
{
handle_dialog_done (self, shortcut_dialog, response);
g_hash_table_remove (self->dialogs, shortcut_dialog);
}
static gboolean
handle_bind_shortcuts (CcGlobalShortcutsProvider *self,
GDBusMethodInvocation *invocation,
const char *app_id,
const char *parent_window,
GVariant *shortcuts)
{
g_autoptr(CcGlobalShortcutDialog) shortcut_dialog = NULL;
shortcut_dialog =
cc_global_shortcut_dialog_new (app_id,
parent_window,
shortcuts);
g_signal_connect (shortcut_dialog, "done",
G_CALLBACK (on_dialog_done),
self);
gtk_application_add_window (self->app, GTK_WINDOW (shortcut_dialog));
g_hash_table_insert (self->dialogs,
g_object_ref (shortcut_dialog),
g_object_ref (invocation));
cc_global_shortcut_dialog_present (shortcut_dialog);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static void
cc_global_shortcuts_provider_init (CcGlobalShortcutsProvider *self)
{
self->dialogs = g_hash_table_new_full (NULL, NULL,
(GDestroyNotify) gtk_window_destroy,
(GDestroyNotify) g_object_unref);
self->skeleton = cc_settings_global_shortcuts_provider_skeleton_new ();
g_signal_connect_swapped (self->skeleton, "handle-bind-shortcuts",
G_CALLBACK (handle_bind_shortcuts), self);
}
gboolean
cc_global_shortcuts_provider_dbus_register (CcGlobalShortcutsProvider *self,
GDBusConnection *connection,
const char *object_path,
GError **error)
{
GDBusInterfaceSkeleton *skeleton =
G_DBUS_INTERFACE_SKELETON (self->skeleton);
return g_dbus_interface_skeleton_export (skeleton, connection, object_path, error);
}
void
cc_global_shortcuts_provider_dbus_unregister (CcGlobalShortcutsProvider *self,
GDBusConnection *connection,
const char *object_path)
{
GDBusInterfaceSkeleton *skeleton =
G_DBUS_INTERFACE_SKELETON (self->skeleton);
if (g_dbus_interface_skeleton_has_connection (skeleton, connection))
g_dbus_interface_skeleton_unexport_from_connection (skeleton, connection);
}
static void
cc_global_shortcuts_provider_dispose (GObject *object)
{
CcGlobalShortcutsProvider *self =
CC_GLOBAL_SHORTCUTS_PROVIDER (object);
g_clear_object (&self->skeleton);
g_clear_pointer (&self->dialogs, g_hash_table_unref);
G_OBJECT_CLASS (cc_global_shortcuts_provider_parent_class)->dispose (object);
}
static void
cc_global_shortcuts_provider_class_init (CcGlobalShortcutsProviderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = cc_global_shortcuts_provider_dispose;
}
CcGlobalShortcutsProvider *
cc_global_shortcuts_provider_new (GtkApplication *app)
{
CcGlobalShortcutsProvider *self;
self = g_object_new (CC_TYPE_GLOBAL_SHORTCUTS_PROVIDER, NULL);
self->app = app;
return self;
}

View file

@ -0,0 +1,46 @@
/*
* 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
*
*/
#pragma once
#include <gio/gio.h>
#include <glib-object.h>
#include <shell/cc-shell-model.h>
#include "cc-global-shortcuts-provider-generated.h"
G_BEGIN_DECLS
#define CC_TYPE_GLOBAL_SHORTCUTS_PROVIDER (cc_global_shortcuts_provider_get_type ())
G_DECLARE_FINAL_TYPE (CcGlobalShortcutsProvider,
cc_global_shortcuts_provider,
CC, GLOBAL_SHORTCUTS_PROVIDER,
GObject)
CcGlobalShortcutsProvider *cc_global_shortcuts_provider_new (GtkApplication *app);
gboolean cc_global_shortcuts_provider_dbus_register (CcGlobalShortcutsProvider *provider,
GDBusConnection *connection,
const char *object_path,
GError **error);
void cc_global_shortcuts_provider_dbus_unregister (CcGlobalShortcutsProvider *provider,
GDBusConnection *connection,
const char *object_path);
G_END_DECLS

View file

@ -0,0 +1,163 @@
/*
* 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 <gio/gio.h>
#include <glib/gi18n.h>
#include <gxdp.h>
#include <stdlib.h>
#include "control-center-global-shortcuts-provider.h"
#include <shell/cc-panel-loader.h>
#include <shell/cc-shell-model.h>
struct _CcGlobalShortcutsProviderApp
{
AdwApplication parent;
CcGlobalShortcutsProvider *global_shortcuts_provider;
};
G_DEFINE_TYPE (CcGlobalShortcutsProviderApp,
cc_global_shortcuts_provider_app,
ADW_TYPE_APPLICATION)
#define INACTIVITY_TIMEOUT 60 * 1000 /* One minute, in millisends */
static gboolean
cc_global_shortcuts_provider_app_dbus_register (GApplication *application,
GDBusConnection *connection,
const char *object_path,
GError **error)
{
CcGlobalShortcutsProviderApp *self =
CC_GLOBAL_SHORTCUTS_PROVIDER_APP (application);
GApplicationClass *parent_class =
G_APPLICATION_CLASS (cc_global_shortcuts_provider_app_parent_class);
if (!parent_class->dbus_register (application,
connection,
object_path,
error))
return FALSE;
return cc_global_shortcuts_provider_dbus_register (self->global_shortcuts_provider,
connection,
object_path, error);
}
static void
cc_global_shortcuts_provider_app_dbus_unregister (GApplication *application,
GDBusConnection *connection,
const char *object_path)
{
CcGlobalShortcutsProviderApp *self =
CC_GLOBAL_SHORTCUTS_PROVIDER_APP (application);
GApplicationClass *parent_class =
G_APPLICATION_CLASS (cc_global_shortcuts_provider_app_parent_class);
if (self->global_shortcuts_provider)
{
cc_global_shortcuts_provider_dbus_unregister (self->global_shortcuts_provider,
connection,
object_path);
}
parent_class->dbus_unregister (application, connection, object_path);
}
static void
cc_global_shortcuts_provider_app_dispose (GObject *object)
{
CcGlobalShortcutsProviderApp *self =
CC_GLOBAL_SHORTCUTS_PROVIDER_APP (object);
g_clear_object (&self->global_shortcuts_provider);
G_OBJECT_CLASS (cc_global_shortcuts_provider_app_parent_class)->dispose (object);
}
static void
cc_global_shortcuts_provider_app_init (CcGlobalShortcutsProviderApp *self)
{
self->global_shortcuts_provider =
cc_global_shortcuts_provider_new (GTK_APPLICATION (self));
g_application_set_inactivity_timeout (G_APPLICATION (self),
INACTIVITY_TIMEOUT);
/* HACK: get the updated inactivity timeout started */
g_application_hold (G_APPLICATION (self));
g_application_release (G_APPLICATION (self));
}
static void
cc_global_shortcuts_provider_app_startup (GApplication *application)
{
G_APPLICATION_CLASS (cc_global_shortcuts_provider_app_parent_class)->startup (application);
}
static void
cc_global_shortcuts_provider_app_class_init (CcGlobalShortcutsProviderAppClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
object_class->dispose = cc_global_shortcuts_provider_app_dispose;
app_class->dbus_register = cc_global_shortcuts_provider_app_dbus_register;
app_class->dbus_unregister = cc_global_shortcuts_provider_app_dbus_unregister;
app_class->startup = cc_global_shortcuts_provider_app_startup;
}
CcGlobalShortcutsProviderApp *
cc_global_shortcuts_provider_app_get ()
{
static CcGlobalShortcutsProviderApp *singleton;
if (singleton)
return singleton;
singleton = g_object_new (CC_TYPE_GLOBAL_SHORTCUTS_PROVIDER_APP,
"application-id", "org.gnome.Settings.GlobalShortcutsProvider",
"flags", G_APPLICATION_IS_SERVICE,
NULL);
return singleton;
}
int
main (int argc, char **argv)
{
GApplication *app;
g_autoptr (GError) error = NULL;
if (!gxdp_init_gtk (GXDP_SERVICE_CLIENT_TYPE_GLOBAL_SHORTCUTS,
&error))
{
g_warning ("Failed to initialize windowing system connection: %s",
error->message);
return EXIT_FAILURE;
}
app = G_APPLICATION (cc_global_shortcuts_provider_app_get ());
return g_application_run (app, argc, argv);
}

View file

@ -0,0 +1,37 @@
/*
* 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
*
*/
#pragma once
#include <gtk/gtk.h>
#include "cc-global-shortcuts-provider.h"
#include <shell/cc-shell-model.h>
G_BEGIN_DECLS
#define CC_TYPE_GLOBAL_SHORTCUTS_PROVIDER_APP cc_global_shortcuts_provider_app_get_type ()
G_DECLARE_FINAL_TYPE (CcGlobalShortcutsProviderApp,
cc_global_shortcuts_provider_app,
CC, GLOBAL_SHORTCUTS_PROVIDER_APP,
AdwApplication)
CcGlobalShortcutsProviderApp *cc_global_shortcuts_provider_app_get (void);
G_END_DECLS

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/control-center/global-shortcuts-provider">
<file preprocess="xml-stripblanks">cc-global-shortcut-dialog.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,48 @@
service_conf = configuration_data()
service_conf.set('libexecdir', control_center_libexecdir)
service = 'org.gnome.Settings.GlobalShortcutsProvider.service'
configure_file(
input: service + '.in',
output: service,
install: true,
install_dir: join_paths(control_center_datadir, 'dbus-1', 'services'),
configuration: service_conf
)
sources = files(
'cc-global-shortcut-dialog.c',
'cc-global-shortcuts-provider.c',
'control-center-global-shortcuts-provider.c'
)
global_shortcuts_resources = gnome.compile_resources(
'cc-global-shortcuts-provider-resources',
'global-shortcuts-provider.gresource.xml',
c_name: 'cc_global_shortcut_provider',
export: true
)
sources += global_shortcuts_resources
sources += gnome.gdbus_codegen(
'cc-global-shortcuts-provider-generated',
'org.gnome.GlobalShortcutsProvider.xml',
interface_prefix: 'org.gnome.',
namespace: 'Cc'
)
executable(
'gnome-control-center-global-shortcuts-provider',
sources,
include_directories: [top_inc],
dependencies: [
shell_deps,
keyboard_panel_dep,
dependency('libgxdp'),
],
c_args: cflags,
install: true,
install_dir: control_center_libexecdir
)

View file

@ -0,0 +1,54 @@
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
<!--
org.gnome.Settings.GlobalShortcutsProvider:
@short_description: Global shortcuts portal permissions UI
Provides a UI for the GlobalShortcuts portal, stores preferences, and
notifies the compositor of changes.
-->
<interface name="org.gnome.Settings.GlobalShortcutsProvider">
<!--
BindShortcuts:
@app_id: Application ID
@parent_window: Parent window handle
@shortcuts: Shortcuts to bind
Presents a shortcuts dialog to the user, stores the preference,
and notifies about changes.
The @shortcuts array is made of tuples composed of a shortcut id, and
a vardict with the following keys:
* ``description`` (``s``)
User-readable text describing what the shortcut does. Optional.
* ``preferred_trigger``` (``s`` or ``as``)
An optional preferred shortcut, or a list of strings specifying the
shortcuts, for example: ``['<alt>C', '<ctrl>Q']``
The @results array is made of tuples composed of a shortcut id, and
a vardict with the following keys:
* ``description`` (``s``)
User-readable text describing what the shortcut does. Optional.
* ``shortcut`` (``as``)
A list of shortcuts to bind to the associated shortcut id, for
example: ``['<alt>C', '<ctrl>Q']``
-->
<method name="BindShortcuts">
<arg type="s" name="app_id" direction="in"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="a(sa{sv})" name="shortcuts" direction="in"/>
<arg type="a(sa{sv})" name="results" direction="out"/>
</method>
</interface>
</node>

View file

@ -0,0 +1,3 @@
[D-BUS Service]
Name=org.gnome.Settings.GlobalShortcutsProvider
Exec=@libexecdir@/gnome-control-center-global-shortcuts-provider

59
gnome-control-center.doap Normal file
View file

@ -0,0 +1,59 @@
<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 Settings</name>
<shortdesc xml:lang="en">GNOME's main interface to configure various aspects of the desktop</shortdesc>
<description xml:lang="en">GNOME's main interface to configure various aspects of the desktop</description>
<support-forum rdf:resource="https://discourse.gnome.org/c/desktop/" />
<download-page rdf:resource="https://download.gnome.org/sources/gnome-control-center/" />
<bug-database rdf:resource="https://gitlab.gnome.org/GNOME/gnome-control-center/issues" />
<category rdf:resource="http://api.gnome.org/doap-extensions#core" />
<programming-language>C</programming-language>
<!-- General -->
<maintainer>
<foaf:Person>
<foaf:name>Felipe Borges</foaf:name>
<foaf:mbox rdf:resource="mailto:felipeborges@gnome.org" />
<gnome:userid>felipeborges</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
<foaf:Person>
<foaf:name>Matthijs Velsink</foaf:name>
<foaf:mbox rdf:resource="mailto:mvelsink@gnome.org" />
<gnome:userid>mvelsink</gnome:userid>
</foaf:Person>
</maintainer>
<!-- Bluetooth -->
<maintainer>
<foaf:Person>
<foaf:name>Bastien Nocera</foaf:name>
<foaf:mbox rdf:resource="mailto:hadess@gnome.org" />
<gnome:userid>hadess</gnome:userid>
</foaf:Person>
</maintainer>
<!-- Printers -->
<maintainer>
<foaf:Person>
<foaf:name>Marek Kašík</foaf:name>
<foaf:mbox rdf:resource="mailto:mkasik@redhat.com" />
<gnome:userid>mkasik</gnome:userid>
</foaf:Person>
</maintainer>
<!-- Wacom -->
<maintainer>
<foaf:Person>
<foaf:name>Carlos Garnacho</foaf:name>
<foaf:mbox rdf:resource="mailto:carlosg@gnome.org" />
<gnome:userid>carlosg</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View file

@ -0,0 +1,361 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<refentry id="gnome-control-center">
<refentryinfo>
<title>gnome-control-center</title>
<productname>GNOME</productname>
<authorgroup>
<author>
<contrib>Maintainer</contrib>
<firstname>Bastien</firstname>
<surname>Nocera</surname>
<email>hadess@hadess.net</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>gnome-control-center</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class="manual">User Commands</refmiscinfo>
</refmeta>
<refnamediv>
<refname>gnome-control-center</refname>
<refpurpose>Configure GNOME settings</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>gnome-control-center <arg choice="opt" rep="repeat">OPTION</arg> <arg choice="opt">PANEL</arg> <arg choice="opt" rep="repeat">ARG</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>gnome-control-center</command> is a graphical
user interface to configure various aspects of GNOME.</para>
<para>When run without arguments, the shell displays the
<firstterm>overview</firstterm>, which shows all available
configuration panels. The overview allows to open individual
panels by clicking on them. It also has a search entry
to find panels by searching keywords.</para>
<para>It is also possible to specify a panel name
as commandline argument to go directly to that panel.
Individual panels may accept further arguments to specify
which tab to open (for multi-tabbed panels) or which
item to display (for panels with lists).</para>
<para>Note that <command>gnome-control-center</command>
is not meant to expose each and every setting that is
available. The settings that can be found here represent
what is considered useful and commonly needed options.
For more exotic or uncommon options, you can look at
<command>gnome-tweak-tool</command> or the
<command>gsettings</command> commandline utility.</para>
</refsect1>
<refsect1>
<title>Panels</title>
<para>The following panel names can be specified:</para>
<variablelist>
<varlistentry>
<term><option>background</option></term>
<listitem><para>The background panel lets you
set your desktop background.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>bluetooth</option></term>
<listitem><para>The bluetooth panel lets you
configure your computer's Bluetooth adapter,
and pair the computer with Bluetooth keyboards,
phones, etc.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>color</option></term>
<listitem><para>The color panel can calibrate
monitors, web cams and printers for accurate
color reproduction.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>datetime</option></term>
<listitem><para>The datetime panel lets you
set the timezone and time format.</para>
<para>Some operations in this panel
affect all user accounts on the computer
and require privieges.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>display</option></term>
<listitem><para>The display panel configures
the resolution and arrangement of monitors
and laptop panels. Note that monitors can be
rearranged by drag-and-drop, and you can change
which monitor is your main display by dragging
the black bar.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>info-overview</option></term>
<listitem><para>The info-overview panel shows a general
overview of the system configuration. It also
lets you change the default applications for
various tasks and the handling of removable
media.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>keyboard</option></term>
<listitem><para>The keyboard panel can
change how the keyboard reacts to key
presses and lets you change keyboard
shortcuts or create custom shortcuts.</para>
<para>You can open this panel on a
specific tab by passing <option>typing</option>
or <option>shortcuts</option> as extra
argument.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>mouse</option></term>
<listitem><para>The mouse panel can change
how mice and touchpads react to user
input.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>network</option></term>
<listitem><para>The network panel provides
a view of available network devices (wired,
wireless or mobile) and their current
configuration. It also provides a way to
create new VPN connections and configure
proxy settings.</para>
<para>You can open this panel on a
specific dialog by passing
<option>create-wifi</option>,
<option>connect-hidden-wifi</option>,
<option>connect-3g</option>,
<option>connect-8021x-wifi</option> or
<option>show-device</option> as extra
argument. The last three parameters
require an additional extra argument for
the network object in the form
<replaceable>/org/freedesktop/NetworkManager/Devices/0</replaceable>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>notifications</option></term>
<listitem><para>The notifications panel provides
a way to control the display of notifications.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>online-accounts</option></term>
<listitem><para>The online-accounts panel
shows your configured online accounts and
lets you add or remove accounts.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>power</option></term>
<listitem><para>The power panel shows
the fill level of batteries and can
configure what happens when the computer
is idle or runs out of battery.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>printers</option></term>
<listitem><para>The printers panel shows
all known printers and their status. It is
possible to inspect queued print jobs and
add new printers.</para>
<para>Some operations in this panel require
privileges.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>privacy</option></term>
<listitem><para>The privacy panel allows
to control visible file usage history, temporary
files, and name.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>region</option></term>
<listitem><para>The region panel
contains regional settings such as
the display language, formatting for
times, dates, numbers, and input sources.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>search</option></term>
<listitem><para>The search panel controls
the results visible in the overview, and the
files and folders to be indexed.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>screen</option></term>
<listitem><para>The screen panel contains
settings that control the screen brightness
and screen lock behavior.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>sharing</option></term>
<listitem><para>The sharing panel contains
settings that control what is shared over the
network.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>sound</option></term>
<listitem><para>The sound panel shows
all known sound devices and their configuration,
including volume and balance settings.</para>
<para>You can open this panel on a
specific tab by passing
<option>output</option>,
<option>input</option>,
<option>hardware</option>,
<option>effects</option> or
<option>applications</option> as extra
argument.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>universal-access</option></term>
<listitem><para>The universal-access panel
contains settings for accessible technologies
such as the screen reader, magnifier, screen
keyboard and AccessX options.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>user-accounts</option></term>
<listitem><para>The user-accounts panel
shows all user accounts that exist on the
computer and allows to change them in some
ways, such as changing the user name, password
or permissions. It also allows to create
or remove accounts.</para>
<para>Some operations in this panel require
privileges.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>wacom</option></term>
<listitem><para>the wacom panel shows
connected Wacom graphics tablets and lets
you calibrate and configure such devices.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Options</title>
<variablelist>
<varlistentry>
<term><option>-?</option>, <option>--help</option></term>
<listitem><para>Prints a short help
text and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--version</option></term>
<listitem><para>Prints the program version
and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-v</option>, <option>--verbose</option></term>
<listitem><para>Enables verbose mode.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-l</option>, <option>--list</option></term>
<listitem><para>Lists the available panels
and exits.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-o</option>, <option>--overview</option></term>
<listitem><para>Opens the overview.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-s</option> <replaceable>term</replaceable>, <option>--search</option> <replaceable>term</replaceable></term>
<listitem><para>Sets the following search term.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success 0 is returned, a non-zero failure
code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>gsettings</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

26
man/meson.build Normal file
View file

@ -0,0 +1,26 @@
xsltproc = find_program('xsltproc', required: false)
assert(xsltproc.found(), 'xsltproc is required to build documentation')
xsltproc_cmd = [
xsltproc,
'--output', '@OUTPUT@',
'--nonet',
'--stringparam', 'man.output.quietly', '1',
'--stringparam', 'funcsynopsis.style', 'ansi',
'--stringparam', 'man.th.extra1.suppress', '1',
'--stringparam', 'man.authors.section.enabled', '0',
'--stringparam', 'man.copyright.section.enabled', '0',
'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl',
'@INPUT@'
]
output = meson.project_name() + '.1'
custom_target(
output,
input : meson.project_name() + '.xml',
output : output,
command : xsltproc_cmd,
install : true,
install_dir : join_paths(control_center_mandir, 'man1')
)

404
meson.build Normal file
View file

@ -0,0 +1,404 @@
project(
'gnome-control-center', 'c',
version : '48.2',
license : 'GPL2+',
meson_version : '>= 0.58.0'
)
control_center_prefix = get_option('prefix')
control_center_bindir = join_paths(control_center_prefix, get_option('bindir'))
control_center_datadir = join_paths(control_center_prefix, get_option('datadir'))
control_center_libexecdir = join_paths(control_center_prefix, get_option('libexecdir'))
control_center_localedir = join_paths(control_center_prefix, get_option('localedir'))
control_center_mandir = join_paths(control_center_prefix, get_option('mandir'))
control_center_sysconfdir = join_paths(control_center_prefix, get_option('sysconfdir'))
control_center_pkgdatadir = join_paths(control_center_datadir, meson.project_name())
control_center_desktopdir = join_paths(control_center_datadir, 'applications')
control_center_icondir = join_paths(control_center_datadir, 'icons')
control_center_schemadir = join_paths (control_center_datadir, 'glib-2.0', 'schemas')
control_center_gettext = meson.project_name() + '-2.0'
host_is_linux = host_machine.system().contains('linux')
host_is_linux_not_s390 = host_is_linux and not host_machine.cpu().contains('s390')
cc = meson.get_compiler('c')
config_h = configuration_data()
py = import('python')
python = py.find_installation('python3')
config_h.set_quoted('TEST_NM_PYTHON', python.full_path())
application_id = 'org.gnome.Settings'
if get_option('profile') == 'development'
application_id += '.' + 'Devel'
endif
version_split = meson.project_version().split('.')
# defines
set_defines = [
# package
['APPLICATION_ID', application_id],
['PACKAGE', meson.project_name()],
['PACKAGE_VERSION', meson.project_version()],
['VERSION', meson.project_version()],
['MAJOR_VERSION', version_split[0]],
['PROFILE', get_option('profile')],
# i18n
['GETTEXT_PACKAGE', control_center_gettext]
]
foreach define: set_defines
config_h.set_quoted(define[0], define[1])
endforeach
distributor_logo = get_option('distributor_logo')
if (distributor_logo != '')
config_h.set_quoted('DISTRIBUTOR_LOGO', distributor_logo,
description: 'Define to absolute path of distributor logo')
dark_mode_distributor_logo = get_option('dark_mode_distributor_logo')
if (dark_mode_distributor_logo != '')
config_h.set_quoted('DARK_MODE_DISTRIBUTOR_LOGO', dark_mode_distributor_logo,
description: 'Define to absolute path of distributor logo for use in dark mode')
endif
endif
# meson does not support octal values, so it must be handled as a
# string. See: https://github.com/mesonbuild/meson/issues/2047
config_h.set('USER_DIR_MODE', '0700',
description: 'Permissions for creating the user\'s config, cache and data directories')
# compiler flags
common_flags = ['-DHAVE_CONFIG_H']
# We have so many deprecation warnings that it becomes impossible to notice any other useful
# warning, thus making the compiler output completely useless. Ideally, we should fix all
# of those but, until then, it's better to add this flag so that we avoid adding even more
# warnings by accident. If you want to fix some of those, just build Settings with:
# meson _build -Ddeprecated-declarions=enabled
if get_option('deprecated-declarations').disabled()
common_flags += '-Wno-deprecated-declarations'
endif
# Only add this when optimizing is enabled (default)
optimized_src = '''
#if __OPTIMIZE__ == 0
#error No optimization
#endif
'''
control_center_optimized = get_option('buildtype').contains('optimized') and cc.compiles(optimized_src)
if control_center_optimized
common_flags += '-Wp,-D_FORTIFY_SOURCE=2'
endif
if get_option('buildtype').contains('debug')
test_cflags = [
'-Wcast-align',
'-Wmissing-field-initializers',
'-Wmissing-declarations',
'-Wmissing-prototypes',
'-Wnested-externs',
'-Wno-strict-aliasing',
'-Wno-sign-compare'
]
common_flags += cc.get_supported_arguments(test_cflags)
endif
add_project_arguments(common_flags, language: 'c')
# Check that we meet the dependencies
libgvc = subproject(
'gvc',
default_options: [
'static=true',
'alsa=false',
'package_name=' + control_center_gettext,
'package_version=' + meson.project_version(),
]
)
libgvc_dep = libgvc.get_variable('libgvc_dep')
libadwaita_version = '>= 1.7.alpha'
goa_req_version = '>= 3.51.0'
pulse_req_version = '>= 2.0'
gtk_dep = dependency(
'gtk4',
version: '>= 4.15.2',
fallback: 'gtk',
default_options: [
'introspection=disabled',
'build-demos=false',
'build-testsuite=false',
'build-tests=false',
'build-examples=false',
]
)
libadwaita_dep = dependency(
'libadwaita-1',
version: libadwaita_version,
required: false
)
# FIXME: this is a workaround because interactive-tests don't work with libadwaita as a subproject. See !1754
libadwaita_is_subproject = not libadwaita_dep.found()
if libadwaita_is_subproject
libadwaita_dep = dependency(
'libadwaita-1',
version: libadwaita_version,
fallback: 'libadwaita',
default_options: [
'examples=false',
'introspection=disabled',
'tests=false',
'vapi=false',
],
)
endif
tecla_dep = dependency(
'tecla',
version: '>=47.0',
required: false
)
# Needs to be a subproject since tecla does not declare dependencies
tecla_is_subproject = not tecla_dep.found()
if tecla_is_subproject
tecla = subproject(
'tecla'
)
endif
subproject('libgxdp',
default_options: 'tests=false',
)
accounts_dep = dependency('accountsservice', version: '>= 23.11.69')
colord_dep = dependency('colord', version: '>= 0.1.34')
gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>= 2.23.0')
gio_dep = dependency('gio-2.0')
glib_dep = dependency('glib-2.0', version: '>= 2.76.6')
gnome_desktop_dep = dependency('gnome-desktop-4')
gnome_bg_dep = dependency('gnome-bg-4')
gnome_rr_dep = dependency('gnome-rr-4')
gnome_settings_dep = dependency('gnome-settings-daemon', version: '>= 48.rc', fallback: 'gsd')
goa_dep = dependency('goa-1.0', version: goa_req_version, fallback: 'goa')
gsettings_desktop_dep = dependency('gsettings-desktop-schemas', version: '>= 48.alpha') # actually requires commit 9d55553, just after alpha
libxml_dep = dependency('libxml-2.0')
pulse_dep = dependency('libpulse', version: pulse_req_version)
pulse_mainloop_dep = dependency('libpulse-mainloop-glib', version: pulse_req_version)
upower_glib_dep = dependency('upower-glib', version: '>= 1.90.6')
gudev_dep = dependency('gudev-1.0', version: '>= 232')
x11_dep = dependency('x11', version: '>= 1.8')
xi_dep = dependency('xi', version: '>= 1.2')
epoxy_dep = dependency('epoxy')
gcr_dep = dependency('gcr-4', version: '>= 4.1.0')
pwquality_dep = dependency('pwquality', version: '>= 1.2.2')
m_dep = cc.find_library('m')
common_deps = [
gio_dep,
glib_dep,
gsettings_desktop_dep,
libadwaita_dep,
dependency('gio-unix-2.0'),
dependency('gthread-2.0'),
gtk_dep,
]
polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.103')
# Also verify that polkit ITS files exist:
# https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/491
polkit_files = [ 'gettext/its/polkit.its', 'gettext/its/polkit.loc' ]
foreach polkit_file: polkit_files
r = run_command('build-aux/meson/find_xdg_file.py', polkit_file, check: true)
assert(r.returncode() == 0, 'ITS support missing from polkit, please upgrade or contact your distribution')
endforeach
# Localisation API
if cc.has_header_symbol('langinfo.h', '_NL_TIME_FIRST_WEEKDAY')
config_h.set('HAVE__NL_TIME_FIRST_WEEKDAY', 1,
description: 'Define if langinfo.h supports _NL_TIME_FIRST_WEEKDAY')
endif
# Check for CUPS 1.4 or newer
cups_dep = dependency('cups', version : '>= 1.4', required: false)
assert(cups_dep.found(), 'CUPS 1.4 or newer not found')
# https://bugzilla.gnome.org/show_bug.cgi?id=696766
cups_cflags = []
if cups_dep.version().version_compare('>= 1.6')
cups_cflags += '-D_PPD_DEPRECATED='
endif
# cups headers
check_headers = [
['HAVE_CUPS_CUPS_H', 'cups/cups.h'],
['HAVE_CUPS_PPD_H', 'cups/ppd.h']
]
foreach header: check_headers
assert(cc.has_header(header[1], args: cups_cflags), 'CUPS headers not found: ' + header[1])
endforeach
config_h.set10('HAVE_CUPS_HTTPCONNECT2',
cc.has_function('httpConnect2', dependencies: cups_dep),
description: 'Define if httpConnect2() is available in CUPS')
# IBus support
enable_ibus = get_option('ibus')
if enable_ibus
ibus_dep = dependency('ibus-1.0', version: '>= 1.5.2')
endif
config_h.set('HAVE_IBUS', enable_ibus,
description: 'Defined if IBus support is enabled')
# thunderbolt
config_h.set10('HAVE_FN_EXPLICIT_BZERO',
cc.has_function('explicit_bzero', prefix: '''#include <string.h>'''),
description: 'Define if explicit_bzero is available')
# Snap support
enable_snap = get_option('snap')
if enable_snap
libsoup_dep = dependency('libsoup-3.0')
endif
config_h.set('HAVE_SNAP', enable_snap,
description: 'Define to 1 if Snap support is enabled')
# malcontent support
enable_malcontent = get_option('malcontent')
if enable_malcontent
malcontent_dep = dependency('malcontent-0', version: '>= 0.7.0')
config_h.set('HAVE_MALCONTENT_0_10', malcontent_dep.version().version_compare('>= 0.10.0'),
description: 'Define to 1 if malcontent ≥ 0.10.0')
endif
config_h.set('HAVE_MALCONTENT', enable_malcontent,
description: 'Define to 1 if malcontent support is enabled')
# location services support
location_services = get_option('location-services')
config_h.set('HAVE_LOCATION_SERVICES', location_services.enabled(),
description: 'Whether location services is enabled')
# Snap support and wellbeing panel
json_glib_dep = dependency('json-glib-1.0')
if host_is_linux
# ModemManager
mm_dep = dependency('mm-glib', version: '>= 0.7')
# NetworkManager
network_manager_deps = [
dependency('libnm', version: '>= 1.24.0'),
dependency('libnma-gtk4', version: '>= 1.10.2'),
mm_dep,
]
endif
config_h.set('BUILD_NETWORK', host_is_linux,
description: 'Define to 1 to build the Network panel')
config_h.set('HAVE_NETWORK_MANAGER', host_is_linux,
description: 'Define to 1 if NetworkManager is available')
config_h.set('BUILD_WWAN', host_is_linux,
description: 'Define to 1 to build the WWan panel')
config_h.set('HAVE_WWAN', host_is_linux,
description: 'Define to 1 if WWan is available')
if host_is_linux_not_s390
# gnome-bluetooth
gnome_bluetooth_dep = dependency('gnome-bluetooth-ui-3.0')
libwacom_dep = dependency('libwacom', version: '>= 1.4')
wacom_deps = [
libwacom_dep,
]
else
message('Bluetooth and Wacom panels will not be built (no USB support on this platform)')
message('Thunderbolt panel will not be built (not supported on this platform)')
endif
config_h.set('BUILD_BLUETOOTH', host_is_linux_not_s390,
description: 'Define to 1 to build the Bluetooth panel')
config_h.set('HAVE_BLUETOOTH', host_is_linux_not_s390,
description: 'Define to 1 if bluetooth support is available')
config_h.set('BUILD_WACOM', host_is_linux_not_s390,
description: 'Define to 1 to build the Wacom panel')
config_h.set('HAVE_WACOM', host_is_linux_not_s390,
description: 'Define to 1 if Wacom is supportted')
config_h.set('BUILD_THUNDERBOLT', host_is_linux_not_s390,
description: 'Define to 1 to build the Thunderbolt panel')
gnome = import('gnome')
i18n = import('i18n')
pkg = import('pkgconfig')
po_dir = join_paths(meson.project_source_root(), 'po')
its_dir = join_paths(meson.project_source_root(), 'gettext')
install_subdir(
'gettext',
install_dir: control_center_datadir
)
top_inc = include_directories('.')
shell_inc = include_directories('shell')
subdir('data/icons')
subdir('po')
subdir('panels')
subdir('shell')
subdir('global-shortcuts-provider')
subdir('search-provider')
if get_option('tests')
subdir('tests')
endif
if get_option('documentation')
subdir('man')
endif
gnome.post_install(
glib_compile_schemas: true,
gtk_update_icon_cache: true,
)
configure_file(
output: 'config.h',
configuration: config_h
)
summary({
'Documentation': get_option('documentation'),
'Tests': get_option('tests'),
'Optimized': control_center_optimized,
},
bool_yn: true,
)
summary({
'GNOME Bluetooth': host_is_linux_not_s390,
'NetworkManager': host_is_linux,
'Wacom': host_is_linux_not_s390,
},
section: 'Dependencies',
bool_yn: true,
)
summary({
'IBus': enable_ibus,
'Snap': enable_snap,
'Malcontent': enable_malcontent,
},
section: 'Optional Dependencies',
bool_yn: true,
)

12
meson_options.txt Normal file
View file

@ -0,0 +1,12 @@
option('deprecated-declarations', type: 'feature', value: 'disabled', description: 'build with deprecated declaration warnings')
option('documentation', type: 'boolean', value: false, description: 'build documentation')
option('location-services', type: 'feature', value: 'enabled', description: 'build with location services')
option('ibus', type: 'boolean', value: true, description: 'build with IBus support')
option('privileged_group', type: 'string', value: 'wheel', description: 'name of group that has elevated permissions')
option('snap', type: 'boolean', value: true, description: 'build with Snap support')
option('tests', type: 'boolean', value: true, description: 'build tests')
option('wayland', type: 'boolean', value: true, description: 'build with Wayland support')
option('profile', type: 'combo', choices: ['default','development'], value: 'default')
option('malcontent', type: 'boolean', value: false, description: 'build with malcontent support')
option('distributor_logo', type: 'string', description: 'absolute path to distributor logo for the About panel')
option('dark_mode_distributor_logo', type: 'string', description: 'absolute path to distributor logo dark mode variant')

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/control-center/applications">
<file preprocess="xml-stripblanks">cc-applications-panel.ui</file>
<file preprocess="xml-stripblanks">cc-application-shortcut-dialog.ui</file>
<file preprocess="xml-stripblanks">cc-default-apps-page.ui</file>
<file preprocess="xml-stripblanks">cc-removable-media-settings.ui</file>
<file preprocess="xml-stripblanks">cc-applications-row.ui</file>
<file preprocess="xml-stripblanks">cc-snap-row.ui</file>
</gresource>
</gresources>

View file

@ -0,0 +1,269 @@
/* cc-global-shortcut-dialog.c
*
* Copyright (C) 2010 Intel, Inc
* Copyright (C) 2016 Endless, Inc
* Copyright (C) 2020 System76, Inc.
* Copyright (C) 2022 Purism SPC
* Copyright © 2024 GNOME Foundation 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: Thomas Wood <thomas.wood@intel.com>
* Georges Basile Stavracas Neto <gbsneto@gnome.org>
* Ian Douglas Scott <idscott@system76.com>
* Mohammed Sadiq <sadiq@sadiqpk.org>
* Dorota Czaplejewicz <gnome@dorotac.eu>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <adwaita.h>
#include <config.h>
#include <glib/gi18n.h>
#include "cc-application-shortcut-dialog.h"
#include "cc-global-shortcuts-rebind-generated.h"
#include "cc-keyboard-shortcut-group.h"
#include "cc-util.h"
struct _CcApplicationShortcutDialog
{
AdwDialog parent_instance;
AdwPreferencesPage *shortcut_list;
GtkSizeGroup *accelerator_size_group;
/* Contains `CcKeyboardItem`s */
GListStore *shortcuts;
AdwPreferencesGroup *group;
CcKeyboardManager *manager;
const char *app_id;
};
G_DEFINE_TYPE (CcApplicationShortcutDialog,
cc_application_shortcut_dialog,
ADW_TYPE_DIALOG)
static void
populate_shortcuts_model (CcApplicationShortcutDialog *self,
const char *section_id,
const char *section_title)
{
GtkWidget *group;
self->shortcuts = g_list_store_new (CC_TYPE_KEYBOARD_ITEM);
group = cc_keyboard_shortcut_group_new (G_LIST_MODEL (self->shortcuts),
section_id, NULL,
self->manager,
self->accelerator_size_group);
self->group = ADW_PREFERENCES_GROUP (group);
}
static void
shortcut_changed_cb (CcApplicationShortcutDialog *self)
{
GVariant *shortcuts;
g_autoptr(GError) error = NULL;
g_autoptr(CcGlobalShortcutsRebind) proxy = NULL;
cc_keyboard_manager_store_global_shortcuts (self->manager, self->app_id);
shortcuts = cc_keyboard_manager_get_global_shortcuts (self->manager, self->app_id);
proxy =
cc_global_shortcuts_rebind_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
"org.freedesktop.impl.portal.desktop.gnome",
"/org/gnome/globalshortcuts",
NULL,
&error);
if (!proxy)
{
g_warning ("Can't connect to Global Shortcuts Rebind service: %s",
error->message);
return;
}
cc_global_shortcuts_rebind_call_rebind_shortcuts_sync (proxy,
self->app_id,
shortcuts,
NULL,
&error);
if (error)
{
g_warning ("Can't connect to Global Shortcuts Rebind service: %s",
error->message);
}
}
static void
shortcut_added_cb (CcApplicationShortcutDialog *self,
CcKeyboardItem *item,
const char *section_id,
const char *section_title)
{
if (strcmp (section_id, self->app_id) != 0)
return;
g_list_store_append (self->shortcuts, item);
g_signal_connect_object (item,
"notify::key-combos",
G_CALLBACK (shortcut_changed_cb),
self, G_CONNECT_SWAPPED);
}
static void
on_remove_dialog_response_cb (CcApplicationShortcutDialog *self,
char *response,
AdwMessageDialog *dialog)
{
if (g_strcmp0 (response, "remove") == 0)
{
cc_keyboard_manager_reset_global_shortcuts (self->manager, self->app_id);
adw_dialog_close (ADW_DIALOG (self));
}
}
static void
reset_all_activated_cb (CcApplicationShortcutDialog *self)
{
AdwAlertDialog *dialog;
dialog = ADW_ALERT_DIALOG (adw_alert_dialog_new (_("Remove All Shortcuts?"),
NULL));
/* TRANSLATORS: %s is an app name. */
adw_alert_dialog_format_body (dialog,
_ ("All actions from %s that have been registered for global shortcuts will be removed."),
cc_util_app_id_to_display_name (self->app_id));
adw_alert_dialog_add_responses (dialog,
"cancel", _("_Cancel"),
"remove", _("_Remove"),
NULL);
adw_alert_dialog_set_response_appearance (dialog,
"remove",
ADW_RESPONSE_DESTRUCTIVE);
adw_alert_dialog_set_default_response (dialog,
"cancel");
adw_alert_dialog_set_close_response (dialog,
"cancel");
g_signal_connect_swapped (GTK_WIDGET (dialog),
"response",
G_CALLBACK (on_remove_dialog_response_cb),
self);
adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (self));
}
static void
shortcuts_loaded_cb (CcApplicationShortcutDialog *self)
{
GtkWidget *group, *reset_button = NULL;
adw_preferences_page_add (self->shortcut_list, ADW_PREFERENCES_GROUP (self->group));
group = adw_preferences_group_new ();
reset_button = g_object_new (ADW_TYPE_BUTTON_ROW,
"title", _("_Remove All Shortcuts"),
"use-underline", TRUE,
NULL);
g_signal_connect_object (reset_button, "activated",
G_CALLBACK (reset_all_activated_cb),
self,
G_CONNECT_SWAPPED);
gtk_widget_add_css_class (reset_button, "destructive-action");
adw_preferences_group_add (ADW_PREFERENCES_GROUP (group), reset_button);
adw_preferences_page_add (self->shortcut_list, ADW_PREFERENCES_GROUP (group));
}
static void
cc_application_shortcut_dialog_finalize (GObject *object)
{
CcApplicationShortcutDialog *self = CC_APPLICATION_SHORTCUT_DIALOG (object);
g_clear_object (&self->manager);
g_clear_object (&self->shortcut_list);
g_clear_object (&self->accelerator_size_group);
G_OBJECT_CLASS (cc_application_shortcut_dialog_parent_class)->finalize (object);
}
static void
cc_application_shortcut_dialog_class_init (CcApplicationShortcutDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_application_shortcut_dialog_finalize;
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/control-center/applications/"
"cc-application-shortcut-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, CcApplicationShortcutDialog, shortcut_list);
gtk_widget_class_bind_template_child (widget_class, CcApplicationShortcutDialog, accelerator_size_group);
}
static void
cc_application_shortcut_dialog_init (CcApplicationShortcutDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->manager = cc_keyboard_manager_new ();
g_signal_connect_object (self->manager,
"shortcut-added",
G_CALLBACK (shortcut_added_cb),
self, G_CONNECT_SWAPPED);
g_signal_connect_object (self->manager,
"shortcut-changed",
G_CALLBACK (shortcut_changed_cb),
self, G_CONNECT_SWAPPED);
/* Shortcuts can not get individually removed from this dialog,
* they get rejected in batches */
g_signal_connect_object (self->manager,
"shortcuts-loaded",
G_CALLBACK (shortcuts_loaded_cb),
self, G_CONNECT_SWAPPED);
}
CcApplicationShortcutDialog *
cc_application_shortcut_dialog_new (const char *app_id)
{
CcApplicationShortcutDialog *dialog;
g_autofree char *explanation_str = NULL;
g_autofree char *formatted_name = NULL;
g_autofree char *name = NULL;
dialog = g_object_new (CC_TYPE_APPLICATION_SHORTCUT_DIALOG, NULL);
dialog->app_id = app_id;
populate_shortcuts_model (dialog, app_id, app_id);
cc_keyboard_manager_load_shortcuts (dialog->manager);
name = cc_util_app_id_to_display_name (app_id);
formatted_name = g_strdup_printf ("<b>%s</b>", name);
/* TRANSLATORS: %s is an app name. */
explanation_str =
g_strdup_printf (_("%s has registered the following global shortcuts"), formatted_name);
adw_preferences_page_set_description (dialog->shortcut_list, explanation_str);
return dialog;
}

View file

@ -0,0 +1,37 @@
/* cc-global-shortcut-dialog.h
*
* Copyright (C) 2020 System76, 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: Ian Douglas Scott <idscott@system76.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "keyboard-shortcuts.h"
#include <adwaita.h>
#include <gio/gio.h>
G_BEGIN_DECLS
#define CC_TYPE_APPLICATION_SHORTCUT_DIALOG (cc_application_shortcut_dialog_get_type ())
G_DECLARE_FINAL_TYPE (CcApplicationShortcutDialog, cc_application_shortcut_dialog, CC, APPLICATION_SHORTCUT_DIALOG, AdwDialog)
CcApplicationShortcutDialog *cc_application_shortcut_dialog_new (const char *app_id);
G_END_DECLS

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcApplicationShortcutDialog" parent="AdwDialog">
<property name="content-width">480</property>
<property name="title" translatable="yes">Global Shortcuts</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar" />
</child>
<property name="content">
<object class="AdwPreferencesPage" id="shortcut_list">
<property name="description-centered">True</property>
</object>
</property>
</object>
</property>
</template>
<object class="GtkSizeGroup" id="accelerator_size_group"/>
</interface>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
/* cc-applications-panel.h
*
* Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <shell/cc-panel.h>
G_BEGIN_DECLS
#define CC_TYPE_APPLICATIONS_PANEL (cc_applications_panel_get_type())
G_DECLARE_FINAL_TYPE (CcApplicationsPanel, cc_applications_panel, CC, APPLICATIONS_PANEL, CcPanel)
G_END_DECLS

View file

@ -0,0 +1,602 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcApplicationsPanel" parent="CcPanel">
<!-- App Search Page (Main Landing Page) -->
<child type="subpage">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Apps</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<!-- Searchbar -->
<child type="top">
<object class="GtkSearchBar">
<property name="search-mode-enabled">True</property>
<property name="child">
<object class="AdwClamp">
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<child>
<object class="GtkSearchEntry" id="app_search_entry">
<property name="placeholder_text" translatable="yes">Search apps</property>
<signal name="activate" handler="on_app_search_entry_activated_cb" object="CcApplicationsPanel" swapped="yes" />
<signal name="search-changed" handler="on_app_search_entry_search_changed_cb" object="CcApplicationsPanel" swapped="yes" />
<signal name="stop-search" handler="on_app_search_entry_search_stopped_cb" object="CcApplicationsPanel" swapped="yes" />
</object>
</child>
</object>
</property>
</object>
</child>
<property name="content">
<object class="GtkStack" id="main_page_stack">
<child>
<object class="GtkStackPage">
<property name="name">apps-page</property>
<property name="child">
<object class="AdwPreferencesPage">
<!-- Default Apps Row -->
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="CcListRow">
<property name="title" translatable="yes">_Default Apps</property>
<property name="subtitle" translatable="yes">Set which apps open links, files, and media</property>
<property name="show-arrow">True</property>
<property name="action-name">navigation.push</property>
<property name="action-target">'default-apps'</property>
</object>
</child>
</object>
</child>
<!-- Apps List -->
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkListBox" id="app_listbox">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list" />
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">no-search-results-page</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">edit-find-symbolic</property>
<property name="title" translatable="yes">No Results Found</property>
<property name="description" translatable="yes">Try a different search</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">no-apps-found-page</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">computer-fail-symbolic</property>
<property name="title" translatable="yes">No Apps Found</property>
<child>
<object class="GtkButton" id="install_button">
<property name="label" translatable="yes">_Find in Software</property>
<property name="use_underline">True</property>
<property name="halign">center</property>
<signal name="clicked" handler="open_software_cb" object="CcApplicationsPanel" swapped="yes"/>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">malcontent-not-found-page</property>
<property name="child">
<object class="AdwStatusPage">
<property name="icon-name">dialog-error-symbolic</property>
<property name="title" translatable="yes">Unable to List Apps</property>
<property name="description" translatable="yes" comments="Translators: Parental Controls here refers to the features provided by 'malcontent'">An error has occurred and apps cannot be listed.
Error details: Parental Controls are not installed.</property>
</object>
</property>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<!-- Default Apps Page -->
<child type="subpage">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Default Apps</property>
<property name="tag">default-apps</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="AdwPreferencesPage">
<!-- Default Apps Settings -->
<child>
<object class="CcDefaultAppsPage" id="default_apps_page" />
</child>
<!-- "Automatically Launch Apps" row -->
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Removable Media</property>
<child>
<object class="AdwSwitchRow" id="autorun_never_row">
<property name="title" translatable="yes">Med_ia Autostart</property>
<property name="subtitle" translatable="yes">Start apps or prompt when media is connected</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</child>
<!-- Removable Media Settings -->
<child>
<object class="CcRemovableMediaSettings" id="removable_media_settings" />
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<!-- App Settings Page -->
<child type="subpage">
<object class="AdwNavigationPage" id="app_settings_page">
<property name="title" translatable="yes">App Settings</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwBanner" id="sandbox_banner">
<property name="visible">False</property>
<property name="revealed">True</property>
<property name="title" translatable="yes">App is not sandboxed</property>
<child>
<object class="CcListRowInfoButton" id="sandbox_info_button">
<property name="visible" bind-source="sandbox_banner" bind-property="visible" bind-flags="sync-create|default"/>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="margin-end">3</property>
<property name="text" translatable="yes">App settings cannot be fully enforced for apps which are not sandboxed. These apps may use permissions which are not shown.</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesPage">
<!-- App icon & buttons -->
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="app_icon_image">
<property name="icon-name">org.gnome.Software</property>
<property name="pixel-size">96</property>
<style>
<class name="icon-dropshadow" />
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="app_name_label">
<property name="justify">center</property>
<property name="wrap">True</property>
<style>
<class name="title-1" />
</style>
</object>
</child>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="spacing">18</property>
<child>
<object class="GtkButton" id="launch_button">
<property name="label" translatable="yes">_Open</property>
<property name="use-underline">True</property>
<property name="can-shrink">True</property>
<signal name="clicked" handler="on_launch_button_clicked_cb" object="CcApplicationsPanel" swapped="yes" />
<style>
<class name="pill" />
<class name="suggested-action" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="view_details_button">
<property name="label" translatable="yes">App _Details</property>
<property name="use-underline">True</property>
<property name="can-shrink">True</property>
<style>
<class name="pill" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="permissions_group">
<property name="title" translatable="yes">Permissions</property>
<child>
<object class="AdwSwitchRow" id="search_row">
<property name="title" translatable="yes" comments="Translators: This row shows/sets whether the app shown in this page can add results to the GNOME Shell search results. Internally this enables/disables the app's Shell Search Provider">_Search</property>
<property name="subtitle" translatable="yes">Receive system searches and send results</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="search_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="CcListRow" id="no_search_row">
<property name="title" bind-source="search_row" bind-property="title" bind-flags="sync-create"/>
<property name="subtitle" bind-source="search_row" bind-property="subtitle" bind-flags="sync-create"/>
<property name="secondary-label" translatable="yes">Disabled</property>
<property name="activatable">False</property>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="notifications_row">
<property name="title" translatable="yes">_Notifications</property>
<property name="subtitle" translatable="yes">Show system notifications</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="notification_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="background_row">
<property name="title" translatable="yes">_Run in Background</property>
<property name="subtitle" translatable="yes">Allow activity when the app is closed</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="background_cb" swapped="yes"/>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="screenshots_row">
<property name="title" translatable="yes">Scr_eenshots</property>
<property name="subtitle" translatable="yes">Take pictures of the screen at any time</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="screenshot_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="wallpaper_row">
<property name="title" translatable="yes">Change _Wallpaper</property>
<property name="subtitle" translatable="yes">Change the desktop wallpaper</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="wallpaper_cb" swapped="yes"/>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="sounds_row">
<property name="title" translatable="yes">So_unds</property>
<property name="subtitle" translatable="yes">Reproduce sounds</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="sound_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="CcListRow" id="no_sounds_row">
<property name="title" translatable="yes">Sounds</property>
<property name="subtitle" translatable="yes">Reproduce sounds</property>
<property name="secondary-label" translatable="yes">Disabled</property>
<property name="activatable">False</property>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="shortcuts_row">
<property name="title" translatable="yes">_Inhibit Shortcuts</property>
<property name="subtitle" translatable="yes">Block standard keyboard shortcuts</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="shortcuts_cb" swapped="yes"/>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="camera_row">
<property name="title" translatable="yes">C_amera</property>
<property name="subtitle" translatable="yes">Take pictures with the camera</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="camera_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="CcListRow" id="no_camera_row">
<property name="title" translatable="yes">Camera</property>
<property name="subtitle" translatable="yes">Take pictures with the camera</property>
<property name="secondary-label" translatable="yes">Disabled</property>
<property name="activatable">False</property>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="microphone_row">
<property name="title" translatable="yes">_Microphone</property>
<property name="subtitle" translatable="yes">Record audio with the microphone</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="microphone_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="CcListRow" id="no_microphone_row">
<property name="title" translatable="yes">Microphone</property>
<property name="subtitle" translatable="yes">Record audio with the microphone</property>
<property name="secondary-label" translatable="yes">Disabled</property>
<property name="activatable">False</property>
</object>
</child>
<child>
<object class="AdwSwitchRow" id="location_row">
<property name="title" translatable="yes">_Location Services</property>
<property name="subtitle" translatable="yes">Access device location data</property>
<property name="use-underline">True</property>
<signal name="notify::active" handler="location_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
<child>
<object class="CcListRow" id="no_location_row">
<property name="title" translatable="yes">Location Services</property>
<property name="subtitle" translatable="yes">Access device location data</property>
<property name="secondary-label" translatable="yes">Disabled</property>
<property name="activatable">False</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="required_permissions_group">
<child>
<object class="CcListRow" id="builtin_row">
<property name="title" translatable="yes">Re_quired Permissions</property>
<property name="subtitle" translatable="yes">System permissions that the app requires</property>
<property name="show-arrow">True</property>
<property name="action-name">navigation.push</property>
<property name="action-target">'builtin-page'</property>
</object>
</child>
<child>
<object class="CcListRow" id="global_shortcuts_row">
<property name="title" translatable="yes">Global S_hortcuts</property>
<property name="subtitle" translatable="yes">Global actions that have been registered for use</property>
<property name="show-arrow">True</property>
<signal name="activated" handler="global_shortcuts_cb" object="CcApplicationsPanel" swapped="yes" />
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="general_group">
<property name="title" translatable="yes">General</property>
<child>
<object class="CcListRow" id="handler_row">
<property name="title" translatable="yes">_Files &amp;amp; Links</property>
<property name="subtitle" translatable="yes">File and link types that are opened by the app</property>
<property name="show-arrow">True</property>
<property name="action-name">navigation.push</property>
<property name="action-target">'handler-page'</property>
</object>
</child>
<child>
<object class="CcListRow" id="storage_row">
<property name="title" translatable="yes">S_torage</property>
<property name="subtitle" translatable="yes">Disk space being used</property>
<property name="show-arrow">True</property>
<property name="action-name">navigation.push</property>
<property name="action-target">'storage-page'</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<!-- Required Permissions page -->
<child type="subpage">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Required Permissions</property>
<property name="tag">builtin-page</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="AdwPreferencesPage" id="builtin_page">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkListBox" id="builtin_list">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<!-- File & Link Associations page -->
<child type="subpage">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Files &amp; Links</property>
<property name="tag">handler-page</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="AdwPreferencesPage" id="handler_page">
<child>
<object class="AdwPreferencesGroup" id="handler_file_group">
<property name="title" translatable="yes">File Types</property>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="handler_link_group">
<property name="title" translatable="yes">Link Types</property>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwButtonRow" id="handler_reset_button_row">
<property name="title" translatable="yes">_Reset</property>
<property name="use-underline">True</property>
<signal name="activated" handler="handler_reset_cb" object="CcApplicationsPanel" swapped="yes"/>
<style>
<class name="destructive-action"/>
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<!-- Storage page -->
<child type="subpage">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Storage</property>
<property name="tag">storage-page</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="AdwPreferencesPage" id="storage_page">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow" id="storage_page_app_row">
<property name="title" translatable="yes">App</property>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="storage_page_data_row">
<property name="title" translatable="yes">Data</property>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="storage_page_cache_row">
<property name="title" translatable="yes">Cache</property>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="storage_page_total_row">
<property name="title" translatable="yes">Total</property>
<style>
<class name="property"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwButtonRow" id="clear_cache_button_row">
<property name="title" translatable="yes">_Clear Cache</property>
<property name="use-underline">True</property>
<signal name="activated" handler="clear_cache_cb" object="CcApplicationsPanel" swapped="yes"/>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
</template>
<object class="GtkSizeGroup">
<property name="mode">horizontal</property>
<widgets>
<widget name="launch_button" />
<widget name="view_details_button" />
</widgets>
</object>
</interface>

View file

@ -0,0 +1,98 @@
/* cc-applications-row.c
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <glib/gi18n.h>
#include "cc-applications-row.h"
#include "cc-applications-resources.h"
struct _CcApplicationsRow
{
AdwActionRow parent;
GAppInfo *info;
};
G_DEFINE_TYPE (CcApplicationsRow, cc_applications_row, ADW_TYPE_ACTION_ROW)
static void
cc_applications_row_finalize (GObject *object)
{
CcApplicationsRow *self = CC_APPLICATIONS_ROW (object);
g_object_unref (self->info);
G_OBJECT_CLASS (cc_applications_row_parent_class)->finalize (object);
}
static void
cc_applications_row_class_init (CcApplicationsRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_applications_row_finalize;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-applications-row.ui");
}
static void
cc_applications_row_init (CcApplicationsRow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
CcApplicationsRow *
cc_applications_row_new (GAppInfo *info)
{
CcApplicationsRow *self;
g_autoptr(GIcon) icon = NULL;
GtkWidget *w;
self = g_object_new (CC_TYPE_APPLICATIONS_ROW, NULL);
self->info = g_object_ref (info);
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), TRUE);
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), g_app_info_get_display_name (info));
icon = g_app_info_get_icon (info);
if (icon != NULL)
g_object_ref (icon);
else
icon = g_themed_icon_new ("application-x-executable");
w = g_object_new (GTK_TYPE_IMAGE,
"accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
"gicon", icon,
NULL);
gtk_widget_add_css_class (w, "lowres-icon");
gtk_image_set_icon_size (GTK_IMAGE (w), GTK_ICON_SIZE_LARGE);
adw_action_row_add_prefix (ADW_ACTION_ROW (self), w);
return self;
}
GAppInfo *
cc_applications_row_get_info (CcApplicationsRow *self)
{
return self->info;
}

View file

@ -0,0 +1,35 @@
/* cc-applications-row.h
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
#include <adwaita.h>
G_BEGIN_DECLS
#define CC_TYPE_APPLICATIONS_ROW (cc_applications_row_get_type())
G_DECLARE_FINAL_TYPE (CcApplicationsRow, cc_applications_row, CC, APPLICATIONS_ROW, AdwActionRow)
CcApplicationsRow* cc_applications_row_new (GAppInfo *info);
GAppInfo* cc_applications_row_get_info (CcApplicationsRow *row);
G_END_DECLS

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcApplicationsRow" parent="AdwActionRow">
<property name="use-markup">False</property>
<child type="suffix">
<object class="GtkImage">
<property name="icon-name">go-next-symbolic</property>
<property name="accessible-role">presentation</property>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,146 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Mohammed Sadiq <sadiq@sadiqpk.org>
* Copyright (C) 2010 Red Hat, Inc
* Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
*
* 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 BUILD_WWAN
#include <libmm-glib.h>
#endif
#include "cc-default-apps-page.h"
#include "cc-default-apps-row.h"
#include "shell/cc-object-storage.h"
typedef struct
{
const char *content_type;
/* Patterns used to filter supported mime types
when changing preferred applications. NULL
means no other types should be changed */
const char *extra_type_filter;
} DefaultAppData;
struct _CcDefaultAppsPage
{
AdwPreferencesGroup parent;
GtkWidget *web_row;
GtkWidget *mail_row;
GtkWidget *calendar_row;
GtkWidget *music_row;
GtkWidget *video_row;
GtkWidget *photos_row;
GtkWidget *calls_row;
GtkWidget *sms_row;
#ifdef BUILD_WWAN
MMManager *mm_manager;
#endif
};
G_DEFINE_TYPE (CcDefaultAppsPage, cc_default_apps_page, ADW_TYPE_PREFERENCES_GROUP)
#ifdef BUILD_WWAN
static void
update_modem_apps_visibility (CcDefaultAppsPage *self)
{
GList *devices;
gboolean has_mm_objects;
devices = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm_manager));
has_mm_objects = g_list_length (devices) > 0;
gtk_widget_set_visible (self->calls_row, has_mm_objects);
gtk_widget_set_visible (self->sms_row, has_mm_objects);
g_list_free_full (devices, (GDestroyNotify)g_object_unref);
}
#endif
static void
on_row_selected_item_changed (CcDefaultAppsRow *row)
{
cc_default_apps_row_update_default_app (row);
}
static void
cc_default_apps_page_dispose (GObject *object)
{
#ifdef BUILD_WWAN
CcDefaultAppsPage *self = CC_DEFAULT_APPS_PAGE (object);
g_clear_object (&self->mm_manager);
#endif
G_OBJECT_CLASS (cc_default_apps_page_parent_class)->dispose (object);
}
static void
cc_default_apps_page_class_init (CcDefaultAppsPageClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = cc_default_apps_page_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-default-apps-page.ui");
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, web_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, mail_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, calendar_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, music_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, video_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, photos_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, calls_row);
gtk_widget_class_bind_template_child (widget_class, CcDefaultAppsPage, sms_row);
gtk_widget_class_bind_template_callback (widget_class, on_row_selected_item_changed);
}
static void
cc_default_apps_page_init (CcDefaultAppsPage *self)
{
g_type_ensure (CC_TYPE_DEFAULT_APPS_ROW);
gtk_widget_init_template (GTK_WIDGET (self));
#ifdef BUILD_WWAN
if (cc_object_storage_has_object (CC_OBJECT_MMMANAGER))
{
self->mm_manager = cc_object_storage_get_object (CC_OBJECT_MMMANAGER);
g_signal_connect_swapped (self->mm_manager, "object-added",
G_CALLBACK (update_modem_apps_visibility), self);
g_signal_connect_swapped (self->mm_manager, "object-removed",
G_CALLBACK (update_modem_apps_visibility), self);
update_modem_apps_visibility (self);
}
#endif
}
CcDefaultAppsPage *
cc_default_apps_page_new (void)
{
return g_object_new (CC_TYPE_DEFAULT_APPS_PAGE,
NULL);
}

View file

@ -0,0 +1,31 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Mohammed Sadiq <sadiq@sadiqpk.org>
*
* 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 <adwaita.h>
G_BEGIN_DECLS
#define CC_TYPE_DEFAULT_APPS_PAGE (cc_default_apps_page_get_type ())
G_DECLARE_FINAL_TYPE (CcDefaultAppsPage, cc_default_apps_page, CC, DEFAULT_APPS_PAGE, AdwPreferencesGroup)
CcDefaultAppsPage *cc_default_apps_page_new (void);
G_END_DECLS

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcDefaultAppsPage" parent="AdwPreferencesGroup">
<property name="title" translatable="yes">Default Apps</property>
<child>
<object class="CcDefaultAppsRow" id="web_row">
<property name="title" translatable="yes">_Web</property>
<property name="use-underline">True</property>
<property name="content-type">x-scheme-handler/http</property>
<property name="filters">text/html;application/xhtml+xml;x-scheme-handler/https</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="mail_row">
<property name="title" translatable="yes">_Mail</property>
<property name="use_underline">True</property>
<property name="content-type">x-scheme-handler/mailto</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="calendar_row">
<property name="title" translatable="yes">_Calendar</property>
<property name="use-underline">True</property>
<property name="content-type">text/calendar</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="music_row">
<property name="title" translatable="yes">M_usic</property>
<property name="use-underline">True</property>
<property name="content-type">audio/x-vorbis+ogg</property>
<property name="filters">audio/*</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="video_row">
<property name="title" translatable="yes">_Video</property>
<property name="use-underline">True</property>
<property name="content-type">video/x-ogm+ogg</property>
<property name="filters">video/*</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="photos_row">
<property name="title" translatable="yes">_Photos</property>
<property name="use-underline">True</property>
<property name="content-type">image/jpeg</property>
<property name="filters">image/*</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="calls_row">
<property name="visible">False</property>
<property name="title" translatable="yes" comments="Translators: This is a telephone call">Ca_lls</property>
<property name="use-underline">True</property>
<property name="content-type">x-scheme-handler/tel</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
<child>
<object class="CcDefaultAppsRow" id="sms_row">
<property name="visible">False</property>
<property name="title" translatable="yes">_SMS</property>
<property name="use-underline">True</property>
<property name="content-type">x-scheme-handler/sms</property>
<signal name="notify::selected-item" handler="on_row_selected_item_changed"/>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,246 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright 2022 Christopher Davis <christopherdavis@gnome.org>
*
* 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/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <glib/gi18n.h>
#include "cc-default-apps-row.h"
struct _CcDefaultAppsRow
{
AdwComboRow parent_instance;
char *content_type;
char *filters;
GListStore *model;
};
G_DEFINE_FINAL_TYPE (CcDefaultAppsRow, cc_default_apps_row, ADW_TYPE_COMBO_ROW)
enum {
PROP_0,
PROP_CONTENT_TYPE,
PROP_FILTERS,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static char *
get_app_display_name (GAppInfo *info)
{
return g_strdup (g_app_info_get_display_name (info));
}
static void
cc_default_apps_row_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcDefaultAppsRow *self = CC_DEFAULT_APPS_ROW (object);
switch (prop_id)
{
case PROP_CONTENT_TYPE:
g_value_set_string (value, self->content_type);
break;
case PROP_FILTERS:
g_value_set_string (value, self->filters);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
cc_default_apps_row_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcDefaultAppsRow *self = CC_DEFAULT_APPS_ROW (object);
switch (prop_id)
{
case PROP_CONTENT_TYPE:
self->content_type = g_value_dup_string (value);
break;
case PROP_FILTERS:
self->filters = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
cc_default_apps_row_finalize (GObject *object)
{
CcDefaultAppsRow *self = CC_DEFAULT_APPS_ROW (object);
g_clear_pointer (&self->content_type, g_free);
g_clear_pointer (&self->filters, g_free);
G_OBJECT_CLASS (cc_default_apps_row_parent_class)->finalize (object);
}
static void
cc_default_apps_row_constructed (GObject *object)
{
CcDefaultAppsRow *self;
g_autoptr(GAppInfo) default_app = NULL;
g_autolist(GAppInfo) recommended_apps = NULL;
GList *l;
g_autoptr(GtkExpression) name_expr = NULL;
G_OBJECT_CLASS (cc_default_apps_row_parent_class)->constructed (object);
self = CC_DEFAULT_APPS_ROW (object);
default_app = g_app_info_get_default_for_type (self->content_type, FALSE);
recommended_apps = g_app_info_get_recommended_for_type (self->content_type);
self->model = g_list_store_new (G_TYPE_APP_INFO);
/* Add the default separately because it may not be in the list of recommended apps */
if (G_IS_APP_INFO (default_app))
g_list_store_append (self->model, default_app);
for (l = recommended_apps; l != NULL; l = l->next) {
GAppInfo *app = l->data;
if (!G_IS_APP_INFO (app) || (default_app != NULL && g_app_info_equal (app, default_app)))
continue;
g_list_store_append (self->model, app);
}
if (g_list_model_get_n_items (G_LIST_MODEL (self->model)) == 0)
{
GtkWidget *no_apps_label;
no_apps_label = gtk_label_new (_("No Apps Available"));
gtk_widget_add_css_class (no_apps_label, "dim-label");
adw_action_row_add_suffix (ADW_ACTION_ROW (self), no_apps_label);
}
adw_combo_row_set_model (ADW_COMBO_ROW (self), G_LIST_MODEL (self->model));
name_expr = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0, NULL,
G_CALLBACK (get_app_display_name),
NULL, NULL);
adw_combo_row_set_expression (ADW_COMBO_ROW (self), name_expr);
}
static void
cc_default_apps_row_class_init (CcDefaultAppsRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = cc_default_apps_row_get_property;
object_class->set_property = cc_default_apps_row_set_property;
object_class->constructed = cc_default_apps_row_constructed;
object_class->finalize = cc_default_apps_row_finalize;
properties[PROP_CONTENT_TYPE] =
g_param_spec_string ("content-type",
NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
properties[PROP_FILTERS] =
g_param_spec_string ("filters",
NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
cc_default_apps_row_init (CcDefaultAppsRow *self)
{
}
void
cc_default_apps_row_update_default_app (CcDefaultAppsRow *self)
{
GAppInfo *info;
g_autoptr(GError) error = NULL;
int i;
info = G_APP_INFO (adw_combo_row_get_selected_item (ADW_COMBO_ROW (self)));
if (!info)
return;
if (g_app_info_set_as_default_for_type (info, self->content_type, &error) == FALSE)
{
g_warning ("Failed to set '%s' as the default app for '%s': %s",
g_app_info_get_name (info), self->content_type, error->message);
}
else
{
g_debug ("Set '%s' as the default handler for '%s'",
g_app_info_get_name (info), self->content_type);
}
if (self->filters)
{
g_auto(GStrv) entries = NULL;
const char *const *mime_types;
g_autoptr(GPtrArray) patterns = NULL;
entries = g_strsplit (self->filters, ";", -1);
patterns = g_ptr_array_new_with_free_func ((GDestroyNotify) g_pattern_spec_free);
for (i = 0; entries[i] != NULL; i++)
{
GPatternSpec *pattern = g_pattern_spec_new (entries[i]);
g_ptr_array_add (patterns, pattern);
}
mime_types = g_app_info_get_supported_types (info);
for (i = 0; mime_types && mime_types[i]; i++)
{
int j;
gboolean matched = FALSE;
g_autoptr(GError) local_error = NULL;
for (j = 0; j < patterns->len; j++)
{
GPatternSpec *pattern = g_ptr_array_index (patterns, j);
if (g_pattern_spec_match_string (pattern, mime_types[i]))
matched = TRUE;
}
if (!matched)
continue;
if (g_app_info_set_as_default_for_type (info, mime_types[i], &local_error) == FALSE)
{
g_warning ("Failed to set '%s' as the default app for secondary "
"content type '%s': %s",
g_app_info_get_name (info), mime_types[i], local_error->message);
}
else
{
g_debug ("Set '%s' as the default handler for '%s'",
g_app_info_get_name (info), mime_types[i]);
}
}
}
}

View file

@ -0,0 +1,32 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright 2022 Christopher Davis <christopherdavis@gnome.org>
*
* 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/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <adwaita.h>
G_BEGIN_DECLS
#define CC_TYPE_DEFAULT_APPS_ROW (cc_default_apps_row_get_type ())
G_DECLARE_FINAL_TYPE (CcDefaultAppsRow, cc_default_apps_row, CC, DEFAULT_APPS_ROW, AdwComboRow)
void cc_default_apps_row_update_default_app (CcDefaultAppsRow *self);
G_END_DECLS

View file

@ -0,0 +1,529 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Mohammed Sadiq <sadiq@sadiqpk.org>
* Copyright (C) 2010 Red Hat, Inc
* Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
*
* 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 <glib.h>
#include <glib/gi18n.h>
#include <shell/cc-panel.h>
#include "cc-applications-panel.h"
#include "cc-removable-media-settings.h"
/* Autorun options */
#define PREF_MEDIA_AUTORUN_NEVER "autorun-never"
#define PREF_MEDIA_AUTORUN_X_CONTENT_START_APP "autorun-x-content-start-app"
#define PREF_MEDIA_AUTORUN_X_CONTENT_IGNORE "autorun-x-content-ignore"
#define PREF_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER "autorun-x-content-open-folder"
#define CUSTOM_ITEM_ASK "cc-item-ask"
#define CUSTOM_ITEM_DO_NOTHING "cc-item-do-nothing"
#define CUSTOM_ITEM_OPEN_FOLDER "cc-item-open-folder"
#define MEDIA_HANDLING_SCHEMA "org.gnome.desktop.media-handling"
struct _CcRemovableMediaSettings
{
AdwPreferencesGroup parent;
GtkAppChooserButton *audio_cdda_chooser;
GtkAppChooserButton *dcf_chooser;
GtkAppChooserButton *music_player_chooser;
AdwDialog *other_type_dialog;
AdwActionRow *other_action_row;
GtkBox *other_action_box;
GtkComboBox *other_type_combo_box;
GtkListStore *other_type_list_store;
GtkAppChooserButton *software_chooser;
GtkAppChooserButton *video_dvd_chooser;
GtkAppChooserButton *other_application_chooser;
GSettings *settings;
};
G_DEFINE_TYPE (CcRemovableMediaSettings, cc_removable_media_settings, ADW_TYPE_PREFERENCES_GROUP)
static char **
remove_elem_from_str_array (char **v,
const char *s)
{
GPtrArray *array;
guint idx;
array = g_ptr_array_new ();
for (idx = 0; v[idx] != NULL; idx++) {
if (g_strcmp0 (v[idx], s) == 0) {
continue;
}
g_ptr_array_add (array, v[idx]);
}
g_ptr_array_add (array, NULL);
g_free (v);
return (char **) g_ptr_array_free (array, FALSE);
}
static char **
add_elem_to_str_array (char **v,
const char *s)
{
GPtrArray *array;
guint idx;
array = g_ptr_array_new ();
for (idx = 0; v[idx] != NULL; idx++) {
g_ptr_array_add (array, v[idx]);
}
g_ptr_array_add (array, g_strdup (s));
g_ptr_array_add (array, NULL);
g_free (v);
return (char **) g_ptr_array_free (array, FALSE);
}
static void
autorun_get_preferences (CcRemovableMediaSettings *self,
const char *x_content_type,
gboolean *pref_start_app,
gboolean *pref_ignore,
gboolean *pref_open_folder)
{
g_auto(GStrv) x_content_start_app = NULL;
g_auto(GStrv) x_content_ignore = NULL;
g_auto(GStrv) x_content_open_folder = NULL;
g_return_if_fail (pref_start_app != NULL);
g_return_if_fail (pref_ignore != NULL);
g_return_if_fail (pref_open_folder != NULL);
*pref_start_app = FALSE;
*pref_ignore = FALSE;
*pref_open_folder = FALSE;
x_content_start_app = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_START_APP);
x_content_ignore = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_IGNORE);
x_content_open_folder = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER);
if (x_content_start_app != NULL) {
*pref_start_app = g_strv_contains ((const gchar * const *) x_content_start_app, x_content_type);
}
if (x_content_ignore != NULL) {
*pref_ignore = g_strv_contains ((const gchar * const *) x_content_ignore, x_content_type);
}
if (x_content_open_folder != NULL) {
*pref_open_folder = g_strv_contains ((const gchar * const *) x_content_open_folder, x_content_type);
}
}
static void
autorun_set_preferences (CcRemovableMediaSettings *self,
const char *x_content_type,
gboolean pref_start_app,
gboolean pref_ignore,
gboolean pref_open_folder)
{
g_auto(GStrv) x_content_start_app = NULL;
g_auto(GStrv) x_content_ignore = NULL;
g_auto(GStrv) x_content_open_folder = NULL;
g_assert (x_content_type != NULL);
x_content_start_app = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_START_APP);
x_content_ignore = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_IGNORE);
x_content_open_folder = g_settings_get_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER);
x_content_start_app = remove_elem_from_str_array (x_content_start_app, x_content_type);
if (pref_start_app) {
x_content_start_app = add_elem_to_str_array (x_content_start_app, x_content_type);
}
g_settings_set_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_START_APP, (const gchar * const*) x_content_start_app);
x_content_ignore = remove_elem_from_str_array (x_content_ignore, x_content_type);
if (pref_ignore) {
x_content_ignore = add_elem_to_str_array (x_content_ignore, x_content_type);
}
g_settings_set_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_IGNORE, (const gchar * const*) x_content_ignore);
x_content_open_folder = remove_elem_from_str_array (x_content_open_folder, x_content_type);
if (pref_open_folder) {
x_content_open_folder = add_elem_to_str_array (x_content_open_folder, x_content_type);
}
g_settings_set_strv (self->settings,
PREF_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER, (const gchar * const*) x_content_open_folder);
}
static void
on_custom_item_activated_cb (CcRemovableMediaSettings *self,
const gchar *item,
GtkAppChooser *app_chooser)
{
g_autofree gchar *content_type = NULL;
content_type = gtk_app_chooser_get_content_type (app_chooser);
if (g_strcmp0 (item, CUSTOM_ITEM_ASK) == 0) {
autorun_set_preferences (self, content_type,
FALSE, FALSE, FALSE);
} else if (g_strcmp0 (item, CUSTOM_ITEM_OPEN_FOLDER) == 0) {
autorun_set_preferences (self, content_type,
FALSE, FALSE, TRUE);
} else if (g_strcmp0 (item, CUSTOM_ITEM_DO_NOTHING) == 0) {
autorun_set_preferences (self, content_type,
FALSE, TRUE, FALSE);
}
}
static void
on_chooser_changed_cb (CcRemovableMediaSettings *self,
GtkAppChooser *chooser)
{
g_autoptr(GAppInfo) info = NULL;
g_autofree gchar *content_type = NULL;
info = gtk_app_chooser_get_app_info (chooser);
if (info == NULL)
return;
content_type = gtk_app_chooser_get_content_type (chooser);
autorun_set_preferences (self, content_type,
TRUE, FALSE, FALSE);
g_app_info_set_as_default_for_type (info, content_type, NULL);
}
/* FIXME: Port away from GtkAppChooserButton entirely */
static void
ellipsize_app_chooser (GtkAppChooserButton *button)
{
GtkWidget *child;
g_autoptr (GList) cells = NULL;
GtkCellRenderer *renderer;
g_assert (GTK_IS_APP_CHOOSER_BUTTON (button));
child = gtk_widget_get_first_child (GTK_WIDGET (button));
g_assert (GTK_IS_CELL_LAYOUT (child));
cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (child));
g_assert (g_list_length (cells) > 0);
renderer = g_list_last (cells)->data;
g_assert (GTK_IS_CELL_RENDERER_TEXT (renderer));
g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
}
static void
prepare_chooser (CcRemovableMediaSettings *self,
GtkAppChooserButton *button,
const gchar *heading)
{
gboolean pref_ask;
gboolean pref_start_app;
gboolean pref_ignore;
gboolean pref_open_folder;
g_autoptr(GAppInfo) info = NULL;
g_autofree gchar *content_type = NULL;
content_type = gtk_app_chooser_get_content_type (GTK_APP_CHOOSER (button));
/* fetch preferences for this content type */
autorun_get_preferences (self, content_type,
&pref_start_app, &pref_ignore, &pref_open_folder);
pref_ask = !pref_start_app && !pref_ignore && !pref_open_folder;
info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (button));
/* append the separator only if we have >= 1 apps in the chooser */
if (info != NULL) {
gtk_app_chooser_button_append_separator (button);
}
gtk_app_chooser_button_append_custom_item (button, CUSTOM_ITEM_ASK,
_("Ask what to do"),
NULL);
gtk_app_chooser_button_append_custom_item (button, CUSTOM_ITEM_DO_NOTHING,
_("Do nothing"),
NULL);
gtk_app_chooser_button_append_custom_item (button, CUSTOM_ITEM_OPEN_FOLDER,
_("Open folder"),
NULL);
gtk_app_chooser_button_set_show_dialog_item (button, TRUE);
if (heading)
gtk_app_chooser_button_set_heading (button, _(heading));
if (pref_ask) {
gtk_app_chooser_button_set_active_custom_item (button, CUSTOM_ITEM_ASK);
} else if (pref_ignore) {
gtk_app_chooser_button_set_active_custom_item (button, CUSTOM_ITEM_DO_NOTHING);
} else if (pref_open_folder) {
gtk_app_chooser_button_set_active_custom_item (button, CUSTOM_ITEM_OPEN_FOLDER);
}
g_signal_connect_object (button, "changed",
G_CALLBACK (on_chooser_changed_cb), self, G_CONNECT_SWAPPED);
g_signal_connect_object (button, "custom-item-activated",
G_CALLBACK (on_custom_item_activated_cb), self, G_CONNECT_SWAPPED);
ellipsize_app_chooser (button);
}
static void
on_other_type_combo_box_changed (CcRemovableMediaSettings *self)
{
GtkTreeIter iter;
g_autofree gchar *x_content_type = NULL;
if (!gtk_combo_box_get_active_iter (self->other_type_combo_box, &iter)) {
return;
}
gtk_tree_model_get (GTK_TREE_MODEL (self->other_type_list_store), &iter,
1, &x_content_type,
-1);
if (self->other_application_chooser != NULL) {
gtk_box_remove (self->other_action_box, GTK_WIDGET (self->other_application_chooser));
self->other_application_chooser = NULL;
}
self->other_application_chooser = GTK_APP_CHOOSER_BUTTON (gtk_app_chooser_button_new (x_content_type));
gtk_box_append (self->other_action_box, GTK_WIDGET (self->other_application_chooser));
prepare_chooser (self, self->other_application_chooser, NULL);
adw_action_row_set_activatable_widget (self->other_action_row, GTK_WIDGET (self->other_application_chooser));
}
static gboolean
on_extra_options_dialog_close_attempt (CcRemovableMediaSettings *self)
{
gtk_widget_set_visible (GTK_WIDGET (self->other_type_dialog), FALSE);
if (self->other_application_chooser != NULL) {
gtk_box_remove (self->other_action_box, GTK_WIDGET (self->other_application_chooser));
self->other_application_chooser = NULL;
}
return GDK_EVENT_PROPAGATE;
}
static void
on_extra_options_button_clicked (CcRemovableMediaSettings *self)
{
/* update other_application_chooser */
on_other_type_combo_box_changed (self);
adw_dialog_present (self->other_type_dialog, GTK_WIDGET (self));
}
#define OFFSET(x) (G_STRUCT_OFFSET (CcRemovableMediaSettings, x))
#define WIDGET_FROM_OFFSET(x) (G_STRUCT_MEMBER (GtkWidget*, self, x))
static void
info_panel_setup_media (CcRemovableMediaSettings *self)
{
guint n;
GList *l, *content_types;
GtkTreeIter iter;
struct {
gint widget_offset;
const gchar *content_type;
const gchar *heading;
} const defs[] = {
{ OFFSET (audio_cdda_chooser), "x-content/audio-cdda", N_("Select an app for audio CDs") },
{ OFFSET (video_dvd_chooser), "x-content/video-dvd", N_("Select an app for video DVDs") },
{ OFFSET (music_player_chooser), "x-content/audio-player", N_("Select an app to run when a music player is connected") },
{ OFFSET (dcf_chooser), "x-content/image-dcf", N_("Select an app to run when a camera is connected") },
{ OFFSET (software_chooser), "x-content/unix-software", N_("Select an app for software CDs") },
};
struct {
const gchar *content_type;
const gchar *description;
} const other_defs[] = {
/* translators: these strings are duplicates of shared-mime-info
* strings, just here to fix capitalization of the English originals.
* If the shared-mime-info translation works for your language,
* simply leave these untranslated.
*/
{ "x-content/audio-dvd", N_("audio DVD") },
{ "x-content/blank-bd", N_("blank Blu-ray disc") },
{ "x-content/blank-cd", N_("blank CD disc") },
{ "x-content/blank-dvd", N_("blank DVD disc") },
{ "x-content/blank-hddvd", N_("blank HD DVD disc") },
{ "x-content/video-bluray", N_("Blu-ray video disc") },
{ "x-content/ebook-reader", N_("e-book reader") },
{ "x-content/video-hddvd", N_("HD DVD video disc") },
{ "x-content/image-picturecd", N_("Picture CD") },
{ "x-content/video-svcd", N_("Super Video CD") },
{ "x-content/video-vcd", N_("Video CD") },
{ "x-content/win32-software", N_("Windows software") },
};
for (n = 0; n < G_N_ELEMENTS (defs); n++) {
prepare_chooser (self,
GTK_APP_CHOOSER_BUTTON (WIDGET_FROM_OFFSET (defs[n].widget_offset)),
defs[n].heading);
}
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->other_type_list_store),
1, GTK_SORT_ASCENDING);
content_types = g_content_types_get_registered ();
for (l = content_types; l != NULL; l = l->next) {
char *content_type = l->data;
g_autofree char *description = NULL;
if (!g_str_has_prefix (content_type, "x-content/"))
continue;
for (n = 0; n < G_N_ELEMENTS (defs); n++) {
if (g_content_type_is_a (content_type, defs[n].content_type)) {
goto skip;
}
}
for (n = 0; n < G_N_ELEMENTS (other_defs); n++) {
if (strcmp (content_type, other_defs[n].content_type) == 0) {
const gchar *s = other_defs[n].description;
if (s == _(s))
description = g_content_type_get_description (content_type);
else
description = g_strdup (_(s));
break;
}
}
if (description == NULL) {
g_debug ("Content type '%s' is missing from the info panel", content_type);
description = g_content_type_get_description (content_type);
}
gtk_list_store_append (self->other_type_list_store, &iter);
gtk_list_store_set (self->other_type_list_store, &iter,
0, description,
1, content_type,
-1);
skip:
;
}
g_list_free_full (content_types, g_free);
gtk_combo_box_set_active (self->other_type_combo_box, 0);
g_settings_bind (self->settings,
PREF_MEDIA_AUTORUN_NEVER,
self,
"sensitive",
G_SETTINGS_BIND_INVERT_BOOLEAN);
}
static void
cc_removable_media_settings_finalize (GObject *object)
{
CcRemovableMediaSettings *self = CC_REMOVABLE_MEDIA_SETTINGS (object);
g_clear_object (&self->settings);
G_OBJECT_CLASS (cc_removable_media_settings_parent_class)->finalize (object);
}
static void
cc_removable_media_settings_dispose (GObject *object)
{
CcRemovableMediaSettings *self = CC_REMOVABLE_MEDIA_SETTINGS (object);
g_clear_pointer ((AdwDialog **) &self->other_type_dialog, adw_dialog_force_close);
G_OBJECT_CLASS (cc_removable_media_settings_parent_class)->dispose (object);
}
static void
cc_removable_media_settings_class_init (CcRemovableMediaSettingsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_removable_media_settings_finalize;
object_class->dispose = cc_removable_media_settings_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-removable-media-settings.ui");
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, audio_cdda_chooser);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, dcf_chooser);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, music_player_chooser);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, other_type_dialog);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, other_action_row);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, other_action_box);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, other_type_combo_box);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, other_type_list_store);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, software_chooser);
gtk_widget_class_bind_template_child (widget_class, CcRemovableMediaSettings, video_dvd_chooser);
gtk_widget_class_bind_template_callback (widget_class, on_extra_options_dialog_close_attempt);
gtk_widget_class_bind_template_callback (widget_class, on_extra_options_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_other_type_combo_box_changed);
}
static void
cc_removable_media_settings_init (CcRemovableMediaSettings *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->settings = g_settings_new (MEDIA_HANDLING_SCHEMA);
info_panel_setup_media (self);
}
CcRemovableMediaSettings *
cc_removable_media_settings_new (void)
{
return g_object_new (CC_TYPE_REMOVABLE_MEDIA_SETTINGS,
NULL);
}

View file

@ -0,0 +1,31 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Mohammed Sadiq <sadiq@sadiqpk.org>
*
* 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 <adwaita.h>
G_BEGIN_DECLS
#define CC_TYPE_REMOVABLE_MEDIA_SETTINGS (cc_removable_media_settings_get_type ())
G_DECLARE_FINAL_TYPE (CcRemovableMediaSettings, cc_removable_media_settings, CC, REMOVABLE_MEDIA_SETTINGS, AdwPreferencesGroup)
CcRemovableMediaSettings* cc_removable_media_settings_new (void);
G_END_DECLS

View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcRemovableMediaSettings" parent="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">CD _Audio</property>
<property name="use_underline">True</property>
<property name="activatable_widget">audio_cdda_chooser</property>
<child>
<object class="GtkAppChooserButton" id="audio_cdda_chooser">
<property name="valign">center</property>
<property name="content_type">x-content/audio-cdda</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_DVD Video</property>
<property name="use_underline">True</property>
<property name="activatable_widget">video_dvd_chooser</property>
<child>
<object class="GtkAppChooserButton" id="video_dvd_chooser">
<property name="valign">center</property>
<property name="content_type">x-content/video-dvd</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Music Pla_yer</property>
<property name="use_underline">True</property>
<property name="activatable_widget">music_player_chooser</property>
<child>
<object class="GtkAppChooserButton" id="music_player_chooser">
<property name="valign">center</property>
<property name="content_type">x-content/audio-player</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">P_hotos</property>
<property name="use_underline">True</property>
<property name="activatable_widget">dcf_chooser</property>
<child>
<object class="GtkAppChooserButton" id="dcf_chooser">
<property name="valign">center</property>
<property name="content_type">x-content/image-dcf</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Sof_tware</property>
<property name="use_underline">True</property>
<property name="activatable_widget">software_chooser</property>
<child>
<object class="GtkAppChooserButton" id="software_chooser">
<property name="valign">center</property>
<property name="content_type">x-content/unix-software</property>
</object>
</child>
</object>
</child>
<child>
<object class="CcListRow">
<property name="title" translatable="yes">_Other Media Types</property>
<property name="show-arrow">True</property>
<signal name="activated" handler="on_extra_options_button_clicked" object="CcRemovableMediaSettings"/>
</object>
</child>
</template>
<object class="GtkSizeGroup">
<widgets>
<widget name="audio_cdda_chooser"/>
<widget name="video_dvd_chooser"/>
<widget name="music_player_chooser"/>
<widget name="dcf_chooser"/>
<widget name="software_chooser"/>
</widgets>
</object>
<object class="AdwDialog" id="other_type_dialog">
<property name="title" translatable="yes">Other Media Types</property>
<property name="content-width">360</property>
<property name="content-height">294</property>
<signal name="close-attempt" handler="on_extra_options_dialog_close_attempt" object="CcRemovableMediaSettings" swapped="yes"/>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Type</property>
<property name="use-underline">True</property>
<property name="activatable-widget">other_type_combo_box</property>
<child>
<object class="GtkComboBox" id="other_type_combo_box">
<property name="model">other_type_list_store</property>
<property name="valign">center</property>
<signal name="changed" handler="on_other_type_combo_box_changed" object="CcRemovableMediaSettings" swapped="yes"/>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow" id="other_action_row">
<property name="title" translatable="yes">_Action</property>
<property name="use-underline">True</property>
<child>
<object class="GtkBox" id="other_action_box">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
<object class="GtkListStore" id="other_type_list_store">
<columns>
<!-- column-name description -->
<column type="gchararray"/>
<!-- column-name content-type -->
<column type="gchararray"/>
</columns>
</object>
</interface>

View file

@ -0,0 +1,455 @@
/* cc-snap-row.c
*
* Copyright 2019 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <glib/gi18n.h>
#include "cc-snap-row.h"
#include "cc-applications-resources.h"
#include "cc-snapd-client.h"
#define CHANGE_POLL_TIME 100
struct _CcSnapRow
{
AdwActionRow parent;
GtkSwitch *slot_toggle;
GtkComboBox *slots_combo;
GtkListStore *slots_combo_model;
GCancellable *cancellable;
CcSnapdClient *client;
JsonObject *plug;
JsonObject *connected_slot;
JsonArray *slots;
JsonObject *target_slot;
gchar *change_id;
guint change_timeout;
};
G_DEFINE_TYPE (CcSnapRow, cc_snap_row, ADW_TYPE_ACTION_ROW)
static void
update_state (CcSnapRow *self)
{
gboolean have_single_option;
GtkTreeIter iter;
have_single_option = json_array_get_length (self->slots) == 1;
gtk_widget_set_visible (GTK_WIDGET (self->slot_toggle), have_single_option);
gtk_widget_set_visible (GTK_WIDGET (self->slots_combo), !have_single_option);
gtk_switch_set_active (self->slot_toggle, self->connected_slot != NULL);
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->slots_combo_model), &iter))
{
do
{
JsonObject *slot;
gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1);
if (slot == self->connected_slot)
gtk_combo_box_set_active_iter (self->slots_combo, &iter);
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->slots_combo_model), &iter));
}
}
static void
disable_controls (CcSnapRow *self)
{
gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), FALSE);
gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), FALSE);
}
static void
enable_controls (CcSnapRow *self)
{
gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), TRUE);
gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), TRUE);
}
static void
change_complete (CcSnapRow *self)
{
g_clear_object (&self->client);
g_clear_pointer (&self->target_slot, json_object_unref);
g_clear_pointer (&self->change_id, g_free);
g_clear_handle_id (&self->change_timeout, g_source_remove);
update_state (self);
enable_controls (self);
}
static gboolean
poll_change_cb (gpointer user_data)
{
CcSnapRow *self = user_data;
g_autoptr(JsonObject) change = NULL;
g_autoptr(GError) error = NULL;
change = cc_snapd_client_get_change_sync (self->client, self->change_id, self->cancellable, &error);
if (change == NULL)
{
g_warning ("Failed to monitor change %s: %s", self->change_id, error->message);
change_complete (self);
return G_SOURCE_REMOVE;
}
if (json_object_get_boolean_member (change, "ready"))
{
const gchar *status = json_object_get_string_member (change, "status");
if (g_strcmp0 (status, "Done") == 0)
{
g_clear_pointer (&self->connected_slot, json_object_unref);
self->connected_slot = self->target_slot ? json_object_ref (self->target_slot) : NULL;
}
else
{
g_warning ("Change completed with status %s", status);
}
change_complete (self);
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
static void
monitor_change (CcSnapRow *self, const gchar *change_id)
{
g_free (self->change_id);
self->change_id = g_strdup (change_id);
g_clear_handle_id (&self->change_timeout, g_source_remove);
self->change_timeout = g_timeout_add (CHANGE_POLL_TIME, poll_change_cb, self);
}
static CcSnapdClient *
get_client(CcSnapRow *self)
{
if (self->client == NULL)
self->client = cc_snapd_client_new ();
return self->client;
}
static void
connect_plug (CcSnapRow *self, JsonObject *slot)
{
g_autofree gchar *change_id = NULL;
g_autoptr(GError) error = NULL;
/* already connected */
if (self->connected_slot != NULL &&
g_strcmp0 (json_object_get_string_member (self->connected_slot, "snap"),
json_object_get_string_member (slot, "snap")) == 0 &&
g_strcmp0 (json_object_get_string_member (self->connected_slot, "slot"),
json_object_get_string_member (slot, "slot")) == 0)
return;
disable_controls (self);
change_id = cc_snapd_client_connect_interface_sync (get_client (self),
json_object_get_string_member (self->plug, "snap"),
json_object_get_string_member (self->plug, "plug"),
json_object_get_string_member (slot, "snap"),
json_object_get_string_member (slot, "slot"),
self->cancellable,
&error);
if (change_id == NULL)
{
g_warning ("Failed to connect plug: %s", error->message);
change_complete (self);
return;
}
g_clear_pointer (&self->target_slot, json_object_unref);
self->target_slot = json_object_ref (slot);
monitor_change (self, change_id);
}
static void
disconnect_plug (CcSnapRow *self)
{
g_autofree gchar *change_id = NULL;
g_autoptr(GError) error = NULL;
/* already disconnected */
if (self->connected_slot == NULL)
return;
disable_controls (self);
change_id = cc_snapd_client_disconnect_interface_sync (get_client (self),
json_object_get_string_member (self->plug, "snap"),
json_object_get_string_member (self->plug, "plug"),
"", "",
self->cancellable, &error);
if (change_id == NULL)
{
g_warning ("Failed to disconnect plug: %s", error->message);
change_complete (self);
return;
}
g_clear_pointer (&self->target_slot, json_object_unref);
monitor_change (self, change_id);
}
static void
switch_changed_cb (CcSnapRow *self)
{
if (gtk_switch_get_active (self->slot_toggle))
{
if (json_array_get_length (self->slots) == 1)
connect_plug (self, json_array_get_object_element (self->slots, 0));
}
else
{
disconnect_plug (self);
}
}
static void
combo_changed_cb (CcSnapRow *self)
{
GtkTreeIter iter;
JsonObject *slot = NULL;
if (!gtk_combo_box_get_active_iter (self->slots_combo, &iter))
return;
gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1);
if (slot != NULL)
connect_plug (self, slot);
else
disconnect_plug (self);
}
static const gchar *
make_interface_label (const gchar *interface_name)
{
if (strcmp (interface_name, "account-control") == 0)
return _("Add user accounts and change passwords");
else if (strcmp (interface_name, "alsa") == 0)
return _("Play and record sound");
else if (strcmp (interface_name, "audio-playback") == 0)
return _("Play audio");
else if (strcmp (interface_name, "audio-record") == 0)
return _("Record audio");
else if (strcmp (interface_name, "avahi-observe") == 0)
return _("Detect network devices using mDNS/DNS-SD (Bonjour/zeroconf)");
else if (strcmp (interface_name, "bluetooth-control") == 0)
return _("Access bluetooth hardware directly");
else if (strcmp (interface_name, "bluez") == 0)
return _("Use bluetooth devices");
else if (strcmp (interface_name, "camera") == 0)
return _("Use your camera");
else if (strcmp (interface_name, "cups-control") == 0)
return _("Print documents");
else if (strcmp (interface_name, "joystick") == 0)
return _("Use any connected joystick");
else if (strcmp (interface_name, "docker") == 0)
return _("Allow connecting to the Docker service");
else if (strcmp (interface_name, "firewall-control") == 0)
return _("Configure network firewall");
else if (strcmp (interface_name, "fuse-support") == 0)
return _("Setup and use privileged FUSE filesystems");
else if (strcmp (interface_name, "fwupd") == 0)
return _("Update firmware on this device");
else if (strcmp (interface_name, "hardware-observe") == 0)
return _("Access hardware information");
else if (strcmp (interface_name, "hardware-random-control") == 0)
return _("Provide entropy to hardware random number generator");
else if (strcmp (interface_name, "hardware-random-observe") == 0)
return _("Use hardware-generated random numbers");
else if (strcmp (interface_name, "home") == 0)
return _("Access files in your home folder");
else if (strcmp (interface_name, "libvirt") == 0)
return _("Access libvirt service");
else if (strcmp (interface_name, "locale-control") == 0)
return _("Change system language and region settings");
else if (strcmp (interface_name, "location-control") == 0)
return _("Change location settings and providers");
else if (strcmp (interface_name, "location-observe") == 0)
return _("Access your location");
else if (strcmp (interface_name, "log-observe") == 0)
return _("Read system and application logs");
else if (strcmp (interface_name, "lxd") == 0)
return _("Access LXD service");
else if (strcmp (interface_name, "modem-manager") == 0)
return _("Use and configure modems");
else if (strcmp (interface_name, "mount-observe") == 0)
return _("Read system mount information and disk quotas");
else if (strcmp (interface_name, "mpris") == 0)
return _("Control music and video players");
else if (strcmp (interface_name, "network-control") == 0)
return _("Change low-level network settings");
else if (strcmp (interface_name, "network-manager") == 0)
return _("Access the NetworkManager service to read and change network settings");
else if (strcmp (interface_name, "network-observe") == 0)
return _("Read access to network settings");
else if (strcmp (interface_name, "network-setup-control") == 0)
return _("Change network settings");
else if (strcmp (interface_name, "network-setup-observe") == 0)
return _("Read network settings");
else if (strcmp (interface_name, "ofono") == 0)
return _("Access the ofono service to read and change network settings for mobile telephony");
else if (strcmp (interface_name, "openvswitch") == 0)
return _("Control Open vSwitch hardware");
else if (strcmp (interface_name, "optical-drive") == 0)
return _("Read from CD/DVD");
else if (strcmp (interface_name, "password-manager-service") == 0)
return _("Read, add, change, or remove saved passwords");
else if (strcmp (interface_name, "ppp") == 0)
return _("Access pppd and ppp devices for configuring Point-to-Point Protocol connections");
else if (strcmp (interface_name, "process-control") == 0)
return _("Pause or end any process on the system");
else if (strcmp (interface_name, "pulseaudio") == 0)
return _("Play and record sound");
else if (strcmp (interface_name, "raw-usb") == 0)
return _("Access USB hardware directly");
else if (strcmp (interface_name, "removable-media") == 0)
return _("Read/write files on removable storage devices");
else if (strcmp (interface_name, "screen-inhibit-control") == 0)
return _("Prevent screen sleep/lock");
else if (strcmp (interface_name, "serial-port") == 0)
return _("Access serial port hardware");
else if (strcmp (interface_name, "shutdown") == 0)
return _("Restart or power off the device");
else if (strcmp (interface_name, "snapd-control") == 0)
return _("Install, remove and configure software");
else if (strcmp (interface_name, "storage-framework-service") == 0)
return _("Access Storage Framework service");
else if (strcmp (interface_name, "system-observe") == 0)
return _("Read process and system information");
else if (strcmp (interface_name, "system-trace") == 0)
return _("Monitor and control any running program");
else if (strcmp (interface_name, "time-control") == 0)
return _("Change the date and time");
else if (strcmp (interface_name, "timeserver-control") == 0)
return _("Change time server settings");
else if (strcmp (interface_name, "timezone-control") == 0)
return _("Change the time zone");
else if (strcmp (interface_name, "udisks2") == 0)
return _("Access the UDisks2 service for configuring disks and removable media");
else if (strcmp (interface_name, "upower-observe") == 0)
return _("Access energy usage data");
else if (strcmp (interface_name, "u2f-devices") == 0)
return _("Read/write access to U2F devices exposed");
else
return interface_name;
}
static void
cc_snap_row_finalize (GObject *object)
{
CcSnapRow *self = CC_SNAP_ROW (object);
g_clear_object (&self->cancellable);
g_clear_object (&self->client);
g_clear_pointer (&self->plug, json_object_unref);
g_clear_pointer (&self->slots, json_array_unref);
g_clear_pointer (&self->target_slot, json_object_unref);
g_clear_pointer (&self->change_id, g_free);
g_clear_handle_id (&self->change_timeout, g_source_remove);
self->change_timeout = 0;
G_OBJECT_CLASS (cc_snap_row_parent_class)->finalize (object);
}
static void
cc_snap_row_class_init (CcSnapRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_snap_row_finalize;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-snap-row.ui");
gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slot_toggle);
gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo);
gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo_model);
gtk_widget_class_bind_template_callback (widget_class, combo_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, switch_changed_cb);
}
static void
cc_snap_row_init (CcSnapRow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
CcSnapRow *
cc_snap_row_new (GCancellable *cancellable, JsonObject *plug, JsonArray *slots)
{
CcSnapRow *self;
const gchar *label = NULL;
GtkTreeIter iter;
self = g_object_new (CC_TYPE_SNAP_ROW, NULL);
self->cancellable = g_object_ref (cancellable);
self->plug = json_object_ref (plug);
self->slots = json_array_ref (slots);
if (json_object_has_member (plug, "connections"))
{
JsonArray *connected_slots = json_object_get_array_member (plug, "connections");
JsonObject *connected_slot_ref = json_array_get_object_element (connected_slots, 0);
for (guint i = 0; i < json_array_get_length (slots); i++)
{
JsonObject *slot = json_array_get_object_element (slots, i);
if (g_strcmp0 (json_object_get_string_member (slot, "snap"),
json_object_get_string_member (connected_slot_ref, "snap")) == 0 &&
g_strcmp0 (json_object_get_string_member (slot, "slot"),
json_object_get_string_member (connected_slot_ref, "slot")) == 0)
self->connected_slot = slot;
}
}
label = make_interface_label (json_object_get_string_member (plug, "interface"));
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self), label);
/* Add option into combo box */
gtk_list_store_append (self->slots_combo_model, &iter);
gtk_list_store_set (self->slots_combo_model, &iter, 1, "--", -1);
for (guint i = 0; i < json_array_get_length (slots); i++)
{
JsonObject *slot = json_array_get_object_element (slots, i);
g_autofree gchar *label = NULL;
label = g_strdup_printf ("%s:%s", json_object_get_string_member (slot, "snap"),
json_object_get_string_member (slot, "slot"));
gtk_list_store_append (self->slots_combo_model, &iter);
gtk_list_store_set (self->slots_combo_model, &iter, 0, slot, 1, label, -1);
}
update_state (self);
return self;
}

View file

@ -0,0 +1,35 @@
/* cc-snap-row.h
*
* Copyright 2019 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <adwaita.h>
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
#define CC_TYPE_SNAP_ROW (cc_snap_row_get_type())
G_DECLARE_FINAL_TYPE (CcSnapRow, cc_snap_row, CC, SNAP_ROW, AdwActionRow)
CcSnapRow* cc_snap_row_new (GCancellable *cancellable,
JsonObject *plug,
JsonArray *slots);
G_END_DECLS

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkListStore" id="slots_combo_model">
<columns>
<!-- column-name slot -->
<column type="JsonObject"/>
<!-- column-name label -->
<column type="gchararray"/>
</columns>
</object>
<template class="CcSnapRow" parent="AdwActionRow">
<property name="can-focus">True</property>
<property name="activatable">False</property>
<property name="activatable-widget">slot_toggle</property>
<child>
<object class="GtkSwitch" id="slot_toggle">
<property name="valign">center</property>
<signal name="notify::active" handler="switch_changed_cb" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkComboBox" id="slots_combo">
<property name="valign">center</property>
<property name="model">slots_combo_model</property>
<signal name="changed" handler="combo_changed_cb" swapped="yes"/>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</template>
</interface>

View file

@ -0,0 +1,290 @@
/* cc-snapd-client.c
*
* Copyright 2023 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <libsoup/soup.h>
#include "cc-snapd-client.h"
// Unix socket that snapd communicates on.
#define SNAPD_SOCKET_PATH "/var/run/snapd.socket"
struct _CcSnapdClient
{
GObject parent;
// HTTP connection to snapd.
SoupSession *session;
};
G_DEFINE_TYPE (CcSnapdClient, cc_snapd_client, G_TYPE_OBJECT)
// Make an HTTP request to send to snapd.
static SoupMessage *
make_message (const gchar *method, const gchar *path, JsonNode *request_body)
{
g_autofree gchar *uri = NULL;
SoupMessage *msg;
SoupMessageHeaders *request_headers;
uri = g_strdup_printf("http://localhost%s", path);
msg = soup_message_new (method, uri);
request_headers = soup_message_get_request_headers (msg);
// Allow authentication via polkit.
soup_message_headers_append (request_headers, "X-Allow-Interaction", "true");
if (request_body != NULL)
{
g_autoptr(JsonGenerator) generator = NULL;
g_autofree gchar *body_text = NULL;
gsize body_length;
g_autoptr(GBytes) body_bytes = NULL;
generator = json_generator_new ();
json_generator_set_root (generator, request_body);
body_text = json_generator_to_data (generator, &body_length);
body_bytes = g_bytes_new (body_text, body_length);
soup_message_set_request_body_from_bytes (msg, "application/json", body_bytes);
}
return msg;
}
// Process an HTTP response recveived from snapd.
static JsonObject *
process_body (SoupMessage *msg, GBytes *body, GError **error)
{
const gchar *content_type;
g_autoptr(JsonParser) parser = NULL;
const gchar *body_data;
size_t body_length;
JsonNode *root;
JsonObject *response;
gint64 status_code;
g_autoptr(GError) internal_error = NULL;
content_type = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Content-Type");
if (g_strcmp0 (content_type, "application/json") != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid content type %s returned", content_type);
return NULL;
}
parser = json_parser_new ();
body_data = g_bytes_get_data (body, &body_length);
if (!json_parser_load_from_data (parser, body_data, body_length, &internal_error))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to decode JSON content: %s", internal_error->message);
return NULL;
}
root = json_parser_get_root (parser);
if (!JSON_NODE_HOLDS_OBJECT (root))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Returned JSON not an object");
return NULL;
}
response = json_node_get_object (root);
status_code = json_object_get_int_member (response, "status-code");
if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_ACCEPTED)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid status code %" G_GINT64_FORMAT, status_code);
return NULL;
}
return json_object_ref (response);
}
// Send an HTTP request to snapd and process the response.
static JsonObject *
call_sync (CcSnapdClient *self,
const gchar *method, const gchar *path, JsonNode *request_body,
GCancellable *cancellable, GError **error)
{
g_autoptr(SoupMessage) msg = NULL;
g_autoptr(GBytes) response_body = NULL;
msg = make_message (method, path, request_body);
response_body = soup_session_send_and_read (self->session, msg, cancellable, error);
if (response_body == NULL)
return NULL;
return process_body (msg, response_body, error);
}
// Perform a snap interface action.
static gchar *
call_interfaces_sync (CcSnapdClient *self,
const gchar *action,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
g_autoptr(JsonBuilder) builder = NULL;
g_autoptr(JsonObject) response = NULL;
const gchar *change_id;
builder = json_builder_new();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "action");
json_builder_add_string_value (builder, action);
json_builder_set_member_name (builder, "plugs");
json_builder_begin_array (builder);
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "snap");
json_builder_add_string_value (builder, plug_snap);
json_builder_set_member_name (builder, "plug");
json_builder_add_string_value (builder, plug_name);
json_builder_end_object (builder);
json_builder_end_array (builder);
json_builder_set_member_name (builder, "slots");
json_builder_begin_array (builder);
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "snap");
json_builder_add_string_value (builder, slot_snap);
json_builder_set_member_name (builder, "slot");
json_builder_add_string_value (builder, slot_name);
json_builder_end_object (builder);
json_builder_end_array (builder);
json_builder_end_object (builder);
response = call_sync (self, "POST", "/v2/interfaces",
json_builder_get_root (builder), cancellable, error);
if (response == NULL)
return NULL;
change_id = json_object_get_string_member (response, "change");
return g_strdup (change_id);
}
static void
cc_snapd_client_dispose (GObject *object)
{
CcSnapdClient *self = CC_SNAPD_CLIENT (object);
g_clear_object(&self->session);
G_OBJECT_CLASS (cc_snapd_client_parent_class)->dispose (object);
}
static void
cc_snapd_client_class_init (CcSnapdClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = cc_snapd_client_dispose;
}
static void
cc_snapd_client_init (CcSnapdClient *self)
{
g_autoptr(GSocketAddress) address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
self->session = soup_session_new_with_options ("remote-connectable", address, NULL);
}
CcSnapdClient *
cc_snapd_client_new (void)
{
return g_object_new (CC_TYPE_SNAPD_CLIENT, NULL);
}
JsonObject *
cc_snapd_client_get_snap_sync (CcSnapdClient *self, const gchar *name, GCancellable *cancellable, GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
path = g_strdup_printf ("/v2/snaps/%s", name);
response = call_sync (self, "GET", path, NULL, cancellable, error);
if (response == NULL)
return NULL;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
return NULL;
}
return json_object_ref (result);
}
JsonObject *
cc_snapd_client_get_change_sync (CcSnapdClient *self, const gchar *change_id, GCancellable *cancellable, GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
path = g_strdup_printf ("/v2/changes/%s", change_id);
response = call_sync (self, "GET", path, NULL, cancellable, error);
if (response == NULL)
return NULL;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
return NULL;
}
return json_object_ref (result);
}
gboolean
cc_snapd_client_get_all_connections_sync (CcSnapdClient *self,
JsonArray **plugs, JsonArray **slots,
GCancellable *cancellable, GError **error)
{
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
response = call_sync (self, "GET", "/v2/connections?select=all", NULL, cancellable, error);
if (response == NULL)
return FALSE;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to /v2/connections");
return FALSE;
}
*plugs = json_array_ref (json_object_get_array_member (result, "plugs"));
*slots = json_array_ref (json_object_get_array_member (result, "slots"));
return TRUE;
}
gchar *
cc_snapd_client_connect_interface_sync (CcSnapdClient *self,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
return call_interfaces_sync (self, "connect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
}
gchar *
cc_snapd_client_disconnect_interface_sync (CcSnapdClient *self,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
return call_interfaces_sync (self, "disconnect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
}

View file

@ -0,0 +1,70 @@
/* cc-snapd-client.h
*
* Copyright 2023 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
#define CC_TYPE_SNAPD_CLIENT (cc_snapd_client_get_type())
G_DECLARE_FINAL_TYPE (CcSnapdClient, cc_snapd_client, CC, SNAPD_CLIENT, GObject)
// Creates a client to contact snapd.
CcSnapdClient *cc_snapd_client_new (void);
// Get information on an installed snap.
JsonObject *cc_snapd_client_get_snap_sync (CcSnapdClient *client,
const gchar *name,
GCancellable *cancellable,
GError **error);
// Get information on a snap change.
JsonObject *cc_snapd_client_get_change_sync (CcSnapdClient *client,
const gchar *change_id,
GCancellable *cancellable,
GError **error);
// Get the state of the snap interface connections.
gboolean cc_snapd_client_get_all_connections_sync (CcSnapdClient *client,
JsonArray **plugs,
JsonArray **slots,
GCancellable *cancellable,
GError **error);
// Connect a plug to a slot. Returns the change ID to monitor for completion of this task.
gchar *cc_snapd_client_connect_interface_sync (CcSnapdClient *client,
const gchar *plug_snap,
const gchar *plug_name,
const gchar *slot_snap,
const gchar *slot_name,
GCancellable *cancellable,
GError **error);
// Disconnect a plug to a slot. Returns the change ID to monitor for completion of this task.
gchar *cc_snapd_client_disconnect_interface_sync (CcSnapdClient *client,
const gchar *plug_snap,
const gchar *plug_name,
const gchar *slot_snap,
const gchar *slot_name,
GCancellable *cancellable,
GError **error);
G_END_DECLS

View file

@ -0,0 +1,62 @@
/* globs.c
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include "globs.h"
/* parse mime/globs and return a string->string hash table */
GHashTable *
parse_globs (void)
{
GHashTable *globs;
const gchar * const *dirs;
gint i;
globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i]; i++)
{
g_autofree gchar *file = g_build_filename (dirs[i], "mime", "globs", NULL);
g_autofree gchar *contents = NULL;
if (g_file_get_contents (file, &contents, NULL, NULL))
{
g_auto(GStrv) strv = NULL;
int i;
strv = g_strsplit (contents, "\n", 0);
for (i = 0; strv[i]; i++)
{
g_auto(GStrv) parts = NULL;
if (strv[i][0] == '#' || strv[i][0] == '\0')
continue;
parts = g_strsplit (strv[i], ":", 2);
g_hash_table_insert (globs, g_strdup (parts[0]), g_strdup (parts[1]));
}
}
}
return globs;
}

View file

@ -0,0 +1,29 @@
/* globs.h
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gio/gio.h>
G_BEGIN_DECLS
GHashTable* parse_globs (void);
G_END_DECLS

View file

@ -0,0 +1,15 @@
[Desktop Entry]
Name=Apps
Comment=Control various app permissions and settings
Exec=gnome-control-center applications
# FIXME
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
Icon=org.gnome.Settings-applications-symbolic
Terminal=false
Type=Application
NoDisplay=true
StartupNotify=true
Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-AccountSettings;
OnlyShowIn=GNOME;Unity;
# Translators: Search terms to find the Apps panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
Keywords=application;flatpak;permission;setting;default;preferred;media;autorun;cd;dvd;usb;audio;video;disc;removable;device;system;

View file

@ -0,0 +1,4 @@
install_data(
'scalable/org.gnome.Settings-applications-symbolic.svg',
install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
)

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 2 0 c -1.089844 0 -2 0.910156 -2 2 v 3 c 0 1.089844 0.910156 2 2 2 h 3 c 1.089844 0 2 -0.910156 2 -2 v -3 c 0 -1.089844 -0.910156 -2 -2 -2 z m 8 0 c -1.089844 0 -2 0.910156 -2 2 v 3 c 0 1.089844 0.910156 2 2 2 h 3 c 1.089844 0 2 -0.910156 2 -2 v -3 c 0 -1.089844 -0.910156 -2 -2 -2 z m -8 2 h 3 v 3 h -3 z m 8 0 h 3 v 3 h -3 z m -8 6 c -1.089844 0 -2 0.910156 -2 2 v 3 c 0 1.089844 0.910156 2 2 2 h 3 c 1.089844 0 2 -0.910156 2 -2 v -3 c 0 -1.089844 -0.910156 -2 -2 -2 z m 8 0 c -1.089844 0 -2 0.910156 -2 2 v 3 c 0 1.089844 0.910156 2 2 2 h 3 c 1.089844 0 2 -0.910156 2 -2 v -3 c 0 -1.089844 -0.910156 -2 -2 -2 z m -8 2 h 3 v 3 h -3 z m 8 0 h 3 v 3 h -3 z m 0 0" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 830 B

View file

@ -0,0 +1,74 @@
panels_list += cappletname
desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
i18n.merge_file(
type : 'desktop',
input : desktop + '.in',
output : desktop,
po_dir : po_dir,
install : true,
install_dir : control_center_desktopdir
)
sources = files(
'cc-applications-panel.c',
'cc-applications-row.c',
'cc-application-shortcut-dialog.c',
'cc-default-apps-page.c',
'cc-default-apps-row.c',
'cc-removable-media-settings.c',
'globs.c',
'search.c',
'utils.c',
)
resource_data = files(
'cc-applications-panel.ui',
'cc-default-apps-page.ui',
'cc-removable-media-settings.ui',
)
sources += gnome.compile_resources(
'cc-' + cappletname + '-resources',
cappletname + '.gresource.xml',
c_name : 'cc_' + cappletname,
dependencies: resource_data,
export : true
)
sources += gnome.gdbus_codegen(
'cc-global-shortcuts-rebind-generated',
'org.gnome.GlobalShortcutsRebind.xml',
interface_prefix : 'org.gnome.',
namespace : 'Cc'
)
install_data(
'org.gnome.GlobalShortcutsRebind.xml',
install_dir : control_center_datadir + '/dbus-1/interfaces',
)
deps = common_deps + keyboard_shortcuts_dep
if host_is_linux
deps += mm_dep
endif
if enable_snap
deps += [ json_glib_dep, libsoup_dep ]
sources += [ 'cc-snap-row.c', 'cc-snapd-client.c' ]
endif
if enable_malcontent
deps += malcontent_dep
endif
panels_libs += static_library(
cappletname,
sources : sources,
include_directories : [ top_inc, common_inc ],
dependencies : deps,
c_args : cflags
)
subdir('icons')

View file

@ -0,0 +1,37 @@
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
<!--
org.gnome.GlobalShortcutsRebind:
@short_description: Global shortcuts portal rebind service
Propagates GlobalShortcuts changes requested by user, to the
portal backend and active clients.
-->
<interface name="org.gnome.GlobalShortcutsRebind">
<!--
RebindShortcuts:
@app_id: Application ID
@shortcuts: Rebound shortcuts
Changes current bindings.
The @shortcuts array is made of tuples composed of a shortcut id, and
a vardict with the following keys:
* ``description`` (``s``)
User-readable text describing what the shortcut does. Optional.
* ``shortcuts`` (``as``)
The new shortcut binding, for example: ``['<alt>C', '<ctrl>Q']``
-->
<method name="RebindShortcuts">
<arg type="s" name="app_id" direction="in"/>
<arg type="a(sa{sv})" name="shortcuts" direction="in"/>
</method>
</interface>
</node>

View file

@ -0,0 +1,133 @@
/* search.c
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include "search.h"
#define SHELL_PROVIDER_GROUP "Shell Search Provider"
static void
add_one_provider (GHashTable *search_providers,
GFile *file)
{
g_autoptr(GKeyFile) keyfile = NULL;
g_autoptr(GError) error = NULL;
g_autofree gchar *app_id = NULL;
g_autofree gchar *path = NULL;
gboolean default_disabled;
path = g_file_get_path (file);
keyfile = g_key_file_new ();
g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &error);
if (error != NULL)
{
g_warning ("Error loading %s: %s - search provider will be ignored",
path, error->message);
return;
}
if (!g_key_file_has_group (keyfile, SHELL_PROVIDER_GROUP))
{
g_debug ("Shell search provider group missing from '%s', ignoring", path);
return;
}
app_id = g_key_file_get_string (keyfile, SHELL_PROVIDER_GROUP, "DesktopId", &error);
if (error != NULL)
{
g_warning ("Unable to read desktop ID from %s: %s - search provider will be ignored",
path, error->message);
return;
}
if (g_str_has_suffix (app_id, ".desktop"))
app_id[strlen (app_id) - strlen (".desktop")] = '\0';
default_disabled = g_key_file_get_boolean (keyfile, SHELL_PROVIDER_GROUP, "DefaultDisabled", NULL);
g_hash_table_insert (search_providers, g_strdup (app_id), GINT_TO_POINTER (default_disabled));
}
static void
parse_search_providers_one_dir (GHashTable *search_providers,
const gchar *system_dir)
{
g_autoptr(GFileEnumerator) enumerator = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GFile) providers_location = NULL;
g_autofree gchar *providers_path = NULL;
providers_path = g_build_filename (system_dir, "gnome-shell", "search-providers", NULL);
providers_location = g_file_new_for_path (providers_path);
enumerator = g_file_enumerate_children (providers_location,
"standard::type,standard::name,standard::content-type",
G_FILE_QUERY_INFO_NONE,
NULL, &error);
if (error != NULL)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Error opening %s: %s - search provider configuration won't be possible",
providers_path, error->message);
return;
}
while (TRUE)
{
GFile *provider = NULL;
if (!g_file_enumerator_iterate (enumerator, NULL, &provider, NULL, &error))
{
g_warning ("Error while reading %s: %s - search provider configuration won't be possible",
providers_path, error->message);
return;
}
if (provider == NULL)
break;
add_one_provider (search_providers, provider);
}
}
/* parse gnome-shell/search-provider files and return a string->boolean hash table */
GHashTable *
parse_search_providers (void)
{
GHashTable *search_providers;
const gchar * const *dirs;
gint i;
search_providers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i]; i++)
parse_search_providers_one_dir (search_providers, dirs[i]);
return search_providers;
}

View file

@ -0,0 +1,29 @@
/* globs.h
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gio/gio.h>
G_BEGIN_DECLS
GHashTable* parse_search_providers (void);
G_END_DECLS

257
panels/applications/utils.c Normal file
View file

@ -0,0 +1,257 @@
/* utils.c
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif
#include <config.h>
#include <glib/gi18n.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ftw.h>
#include "utils.h"
#ifdef HAVE_SNAP
#include "cc-snapd-client.h"
#endif
static gint
ftw_remove_cb (const gchar *path,
const struct stat *sb,
gint typeflags,
struct FTW *ftwbuf)
{
remove (path);
return 0;
}
static void
file_remove_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GFile *file = source_object;
g_autofree gchar *path = g_file_get_path (file);
nftw (path, ftw_remove_cb, 20, FTW_PHYS | FTW_DEPTH);
if (g_task_set_return_on_cancel (task, FALSE))
g_task_return_boolean (task, TRUE);
}
void
file_remove_async (GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data)
{
g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread (task, file_remove_thread_func);
}
gboolean
file_remove_finish (GFile *file,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static GPrivate size_key = G_PRIVATE_INIT (g_free);
static gint
ftw_size_cb (const gchar *path,
const struct stat *sb,
gint typeflags,
struct FTW *ftwbuf)
{
guint64 *size = (guint64*)g_private_get (&size_key);
if (typeflags == FTW_F)
*size += sb->st_size;
return 0;
}
static void
file_size_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GFile *file = source_object;
g_autofree gchar *path = g_file_get_path (file);
guint64 *total;
g_private_replace (&size_key, g_new0 (guint64, 1));
nftw (path, ftw_size_cb, 20, FTW_PHYS | FTW_DEPTH);
total = g_new0 (guint64, 1);
*total = *(guint64*)g_private_get (&size_key);
if (g_task_set_return_on_cancel (task, FALSE))
g_task_return_pointer (task, total, g_free);
}
void
file_size_async (GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data)
{
g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data);
g_task_set_return_on_cancel (task, TRUE);
g_task_run_in_thread (task, file_size_thread_func);
}
gboolean
file_size_finish (GFile *file,
GAsyncResult *result,
guint64 *size,
GError **error)
{
g_autofree guint64 *data = NULL;
g_return_val_if_fail (g_task_is_valid (result, file), FALSE);
data = g_task_propagate_pointer (G_TASK (result), error);
if (data == NULL)
return FALSE;
if (size != NULL)
*size = *data;
return TRUE;
}
static gchar *
get_output_of (const gchar **argv)
{
g_autofree gchar *output = NULL;
int status;
if (!g_spawn_sync (NULL,
(gchar**) argv,
NULL,
G_SPAWN_SEARCH_PATH,
NULL, NULL,
&output, NULL,
&status, NULL))
return NULL;
if (!g_spawn_check_wait_status (status, NULL))
return NULL;
return g_steal_pointer (&output);
}
GKeyFile *
get_flatpak_metadata (const gchar *app_id)
{
const gchar *argv[5] = { "flatpak", "info", "-m", "app", NULL };
g_autofree gchar *data = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GKeyFile) keyfile = NULL;
argv[3] = app_id;
data = get_output_of (argv);
if (data == NULL)
return NULL;
keyfile = g_key_file_new ();
if (!g_key_file_load_from_data (keyfile, data, -1, 0, &error))
{
g_warning ("%s", error->message);
return NULL;
}
return g_steal_pointer (&keyfile);
}
guint64
get_flatpak_app_size (const gchar *app_id)
{
const gchar *argv[5] = { "flatpak", "info", "-s", "app", NULL };
g_autofree gchar *data = NULL;
guint64 factor;
double val;
argv[3] = app_id;
data = get_output_of (argv);
if (data == NULL)
return 0;
data = g_strstrip (data);
if (g_str_has_suffix (data, "kB") || g_str_has_suffix (data, "kb"))
factor = 1000;
else if (g_str_has_suffix (data, "MB") || g_str_has_suffix (data, "Mb"))
factor = 1000 * 1000;
else if (g_str_has_suffix (data, "GB") || g_str_has_suffix (data, "Gb"))
factor = 1000 * 1000 * 1000;
else if (g_str_has_suffix (data, "KiB") || g_str_has_suffix (data, "Kib"))
factor = 1024;
else if (g_str_has_suffix (data, "MiB") || g_str_has_suffix (data, "Mib"))
factor = 1024 * 1024;
else if (g_str_has_suffix (data, "GiB") || g_str_has_suffix (data, "Gib"))
factor = 1024 * 1024 * 1024;
else
factor = 1;
val = g_ascii_strtod (data, NULL);
return (guint64)(val * factor);
}
guint64
get_snap_app_size (const gchar *snap_name)
{
#ifdef HAVE_SNAP
g_autoptr(CcSnapdClient) client = NULL;
g_autoptr(JsonObject) snap = NULL;
g_autoptr(GError) error = NULL;
client = cc_snapd_client_new ();
snap = cc_snapd_client_get_snap_sync (client, snap_name, NULL, &error);
if (snap == NULL)
{
g_warning ("Failed to get snap size: %s", error->message);
return 0;
}
return json_object_get_int_member (snap, "installed-size");
#else
return 0;
#endif
}
char *
get_app_id (GAppInfo *info)
{
gchar *app_id = g_strdup (g_app_info_get_id (info));
if (g_str_has_suffix (app_id, ".desktop"))
app_id[strlen (app_id) - strlen (".desktop")] = '\0';
return app_id;
}

View file

@ -0,0 +1,55 @@
/* utils.h
*
* Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gio/gio.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
void file_remove_async (GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data);
gboolean file_remove_finish (GFile *file,
GAsyncResult *result,
GError **error);
void file_size_async (GFile *file,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data);
gboolean file_size_finish (GFile *file,
GAsyncResult *result,
guint64 *size,
GError **error);
GKeyFile* get_flatpak_metadata (const gchar *app_id);
guint64 get_flatpak_app_size (const gchar *app_id);
guint64 get_snap_app_size (const gchar *snap_name);
gchar* get_app_id (GAppInfo *info);
G_END_DECLS

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/control-center/background">
<file preprocess="xml-stripblanks">cc-background-chooser.ui</file>
<file preprocess="xml-stripblanks">cc-background-panel.ui</file>
<file preprocess="xml-stripblanks">cc-background-preview.ui</file>
<file>preview.css</file>
</gresource>
<gresource prefix="/org/gnome/Settings">
<file preprocess="xml-stripblanks">icons/scalable/actions/background-selected-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/slideshow-symbolic.svg</file>
</gresource>
</gresources>

View file

@ -0,0 +1,454 @@
/* bg-recent-source.c
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "bg-recent-source"
#include "bg-recent-source.h"
#include "cc-background-item.h"
#define ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_NAME "," \
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
G_FILE_ATTRIBUTE_TIME_MODIFIED
struct _BgRecentSource
{
BgSource parent;
GFile *backgrounds_folder;
GFileMonitor *monitor;
GCancellable *cancellable;
GHashTable *items;
};
G_DEFINE_TYPE (BgRecentSource, bg_recent_source, BG_TYPE_SOURCE)
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
CcBackgroundItem *item_a;
CcBackgroundItem *item_b;
guint64 modified_a;
guint64 modified_b;
int retval;
item_a = (CcBackgroundItem *) a;
item_b = (CcBackgroundItem *) b;
modified_a = cc_background_item_get_modified (item_a);
modified_b = cc_background_item_get_modified (item_b);
retval = modified_b - modified_a;
return retval;
}
static void
add_file_from_info (BgRecentSource *self,
GFile *file,
GFileInfo *info)
{
g_autoptr(CcBackgroundItem) item = NULL;
g_autofree gchar *source_uri = NULL;
g_autofree gchar *uri = NULL;
GListStore *store;
const gchar *content_type;
guint64 mtime;
content_type = g_file_info_get_content_type (info);
mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
if (!content_type || !g_content_type_is_a (content_type, "image/*"))
return;
uri = g_file_get_uri (file);
item = cc_background_item_new (uri);
g_object_set (G_OBJECT (item),
"shading", G_DESKTOP_BACKGROUND_SHADING_SOLID,
"placement", G_DESKTOP_BACKGROUND_STYLE_ZOOM,
"modified", mtime,
"needs-download", FALSE,
"source-url", source_uri,
NULL);
store = bg_source_get_liststore (BG_SOURCE (self));
g_list_store_insert_sorted (store, item, sort_func, self);
g_hash_table_insert (self->items, g_strdup (uri), g_object_ref (item));
}
static void
remove_item (BgRecentSource *self,
CcBackgroundItem *item)
{
GListStore *store;
const gchar *uri;
guint i;
g_return_if_fail (BG_IS_RECENT_SOURCE (self));
g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
uri = cc_background_item_get_uri (item);
store = bg_source_get_liststore (BG_SOURCE (self));
g_debug ("Removing wallpaper %s", uri);
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
{
g_autoptr(CcBackgroundItem) tmp = NULL;
tmp = g_list_model_get_item (G_LIST_MODEL (store), i);
if (tmp == item)
{
g_list_store_remove (store, i);
break;
}
}
g_hash_table_remove (self->items, cc_background_item_get_uri (item));
}
static void
query_info_finished_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
BgRecentSource *self;
g_autoptr(GFileInfo) file_info = NULL;
g_autoptr(GError) error = NULL;
GFile *file = NULL;
file = G_FILE (source);
file_info = g_file_query_info_finish (file, result, &error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Could not get pictures file information: %s", error->message);
return;
}
self = BG_RECENT_SOURCE (user_data);
g_debug ("Adding wallpaper %s (%d)",
g_file_info_get_name (file_info),
G_IS_FILE (self->backgrounds_folder));
add_file_from_info (self, file, file_info);
}
static void
on_file_changed_cb (BgRecentSource *self,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type)
{
g_autofree gchar *uri = NULL;
switch (event_type)
{
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
g_file_query_info_async (file,
ATTRIBUTES,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
G_PRIORITY_DEFAULT,
self->cancellable,
query_info_finished_cb,
self);
break;
case G_FILE_MONITOR_EVENT_DELETED:
uri = g_file_get_uri (file);
remove_item (self, g_hash_table_lookup (self->items, uri));
break;
default:
return;
}
}
static int
file_sort_func (gconstpointer a,
gconstpointer b)
{
GFileInfo *file_a = G_FILE_INFO (a);
GFileInfo *file_b = G_FILE_INFO (b);
guint64 modified_a, modified_b;
modified_a = g_file_info_get_attribute_uint64 (file_a, G_FILE_ATTRIBUTE_TIME_MODIFIED);
modified_b = g_file_info_get_attribute_uint64 (file_b, G_FILE_ATTRIBUTE_TIME_MODIFIED);
return modified_b - modified_a;
}
static void
file_info_async_ready_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
BgRecentSource *self;
g_autolist(GFileInfo) file_infos = NULL;
g_autoptr(GError) error = NULL;
GFile *parent = NULL;
GList *l;
file_infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
result,
&error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Could not get pictures file information: %s", error->message);
return;
}
self = BG_RECENT_SOURCE (user_data);
parent = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
file_infos = g_list_sort (file_infos, file_sort_func);
for (l = file_infos; l; l = l->next)
{
g_autoptr(GFile) file = NULL;
GFileInfo *info;
info = l->data;
file = g_file_get_child (parent, g_file_info_get_name (info));
g_debug ("Found recent wallpaper %s", g_file_info_get_name (info));
add_file_from_info (self, file, info);
}
g_file_enumerator_close (G_FILE_ENUMERATOR (source), self->cancellable, &error);
if (error)
g_warning ("Error closing file enumerator: %s", error->message);
}
static void
enumerate_children_finished_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
BgRecentSource *self;
g_autoptr(GFileEnumerator) enumerator = NULL;
g_autoptr(GError) error = NULL;
enumerator = g_file_enumerate_children_finish (G_FILE (source), result, &error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Could not fill pictures source: %s", error->message);
return;
}
self = BG_RECENT_SOURCE (user_data);
g_file_enumerator_next_files_async (enumerator,
G_MAXINT,
G_PRIORITY_DEFAULT,
self->cancellable,
file_info_async_ready_cb,
self);
}
static void
load_backgrounds (BgRecentSource *self)
{
g_autofree gchar *backgrounds_path = NULL;
g_autoptr(GError) error = NULL;
if (!g_file_make_directory_with_parents (self->backgrounds_folder, self->cancellable, &error) &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
{
g_critical ("Failed to create local background directory: %s", error->message);
return;
}
backgrounds_path = g_file_get_path (self->backgrounds_folder);
g_debug ("Enumerating wallpapers under %s", backgrounds_path);
g_file_enumerate_children_async (self->backgrounds_folder,
ATTRIBUTES,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
G_PRIORITY_DEFAULT,
self->cancellable,
enumerate_children_finished_cb,
self);
self->monitor = g_file_monitor_directory (self->backgrounds_folder,
G_FILE_MONITOR_WATCH_MOVES,
self->cancellable,
&error);
if (!self->monitor)
{
g_critical ("Failed to monitor background directory: %s", error->message);
return;
}
g_signal_connect_object (self->monitor, "changed", G_CALLBACK (on_file_changed_cb), self, G_CONNECT_SWAPPED);
}
/* Callbacks */
static void
on_file_copied_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
g_autofree gchar *original_file = NULL;
g_autoptr(GError) error = NULL;
g_file_copy_finish (G_FILE (source), result, &error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_critical ("Failed to copy file: %s", error->message);
return;
}
original_file = g_file_get_path (G_FILE (source));
g_debug ("Successfully copied wallpaper: %s", original_file);
}
static void
on_file_deleted_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(BgRecentSource) self = BG_RECENT_SOURCE (user_data);
g_autofree gchar *original_file = NULL;
g_autoptr(GError) error = NULL;
g_file_delete_finish (G_FILE (source), result, &error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_critical ("Failed to delete wallpaper: %s", error->message);
return;
}
original_file = g_file_get_path (G_FILE (source));
g_debug ("Successfully deleted wallpaper: %s", original_file);
}
/* GObject overrides */
static void
bg_recent_source_finalize (GObject *object)
{
BgRecentSource *self = (BgRecentSource *)object;
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
g_clear_object (&self->monitor);
G_OBJECT_CLASS (bg_recent_source_parent_class)->finalize (object);
}
static void
bg_recent_source_class_init (BgRecentSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = bg_recent_source_finalize;
}
static void
bg_recent_source_init (BgRecentSource *self)
{
g_autofree gchar *backgrounds_path = NULL;
backgrounds_path = g_build_filename (g_get_user_data_dir (), "backgrounds", NULL);
self->items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
self->cancellable = g_cancellable_new ();
self->backgrounds_folder = g_file_new_for_path (backgrounds_path);
load_backgrounds (self);
}
BgRecentSource*
bg_recent_source_new (void)
{
return g_object_new (BG_TYPE_RECENT_SOURCE, NULL);
}
void
bg_recent_source_add_file (BgRecentSource *self,
const gchar *path)
{
g_autoptr(GDateTime) now = NULL;
g_autofree gchar *destination_name = NULL;
g_autofree gchar *formatted_now = NULL;
g_autofree gchar *basename = NULL;
g_autoptr(GFile) destination = NULL;
g_autoptr(GFile) file = NULL;
g_return_if_fail (BG_IS_RECENT_SOURCE (self));
g_return_if_fail (path && *path);
g_debug ("Importing wallpaper %s", path);
now = g_date_time_new_now_local ();
formatted_now = g_date_time_format (now, "%Y-%m-%d-%H-%M-%S");
file = g_file_new_for_path (path);
basename = g_file_get_basename (file);
destination_name = g_strdup_printf ("%s-%s", formatted_now, basename);
destination = g_file_get_child (self->backgrounds_folder, destination_name);
g_file_copy_async (file,
destination,
G_FILE_COPY_NONE,
G_PRIORITY_DEFAULT,
self->cancellable,
NULL, NULL,
on_file_copied_cb,
g_object_ref (self));
}
void
bg_recent_source_remove_item (BgRecentSource *self,
CcBackgroundItem *item)
{
g_autoptr(GFile) file = NULL;
const gchar *uri;
g_return_if_fail (BG_IS_RECENT_SOURCE (self));
g_return_if_fail (CC_IS_BACKGROUND_ITEM (item));
uri = cc_background_item_get_uri (item);
file = g_file_new_for_uri (uri);
g_file_delete_async (file,
G_PRIORITY_DEFAULT,
self->cancellable,
on_file_deleted_cb,
g_object_ref (self));
}

View file

@ -0,0 +1,39 @@
/* bg-recent-source.h
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "bg-source.h"
#include "cc-background-item.h"
G_BEGIN_DECLS
#define BG_TYPE_RECENT_SOURCE (bg_recent_source_get_type())
G_DECLARE_FINAL_TYPE (BgRecentSource, bg_recent_source, BG, RECENT_SOURCE, BgSource)
BgRecentSource* bg_recent_source_new (void);
void bg_recent_source_add_file (BgRecentSource *self,
const gchar *path);
void bg_recent_source_remove_item (BgRecentSource *self,
CcBackgroundItem *item);
G_END_DECLS

View file

@ -0,0 +1,102 @@
/*
* 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: Thomas Wood <thomas.wood@intel.com>
*
*/
#include "bg-source.h"
#include "cc-background-item.h"
#include <cairo-gobject.h>
typedef struct
{
GListStore *store;
} BgSourcePrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (BgSource, bg_source, G_TYPE_OBJECT)
enum
{
PROP_0,
PROP_LISTSTORE
};
static void
bg_source_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
BgSource *source = BG_SOURCE (object);
switch (property_id)
{
case PROP_LISTSTORE:
g_value_set_object (value, bg_source_get_liststore (source));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
bg_source_dispose (GObject *object)
{
BgSource *source = BG_SOURCE (object);
BgSourcePrivate *priv = bg_source_get_instance_private (source);
g_clear_object (&priv->store);
G_OBJECT_CLASS (bg_source_parent_class)->dispose (object);
}
static void
bg_source_class_init (BgSourceClass *klass)
{
GParamSpec *pspec;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = bg_source_get_property;
object_class->dispose = bg_source_dispose;
pspec = g_param_spec_object ("liststore",
"Liststore",
"Liststore used in the source",
G_TYPE_LIST_STORE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_LISTSTORE, pspec);
}
static void
bg_source_init (BgSource *self)
{
BgSourcePrivate *priv = bg_source_get_instance_private (self);
priv->store = g_list_store_new (CC_TYPE_BACKGROUND_ITEM);
}
GListStore*
bg_source_get_liststore (BgSource *source)
{
BgSourcePrivate *priv;
g_return_val_if_fail (BG_IS_SOURCE (source), NULL);
priv = bg_source_get_instance_private (source);
return priv->store;
}

View file

@ -0,0 +1,38 @@
/*
* 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: Thomas Wood <thomas.wood@intel.com>
*
*/
#pragma once
#include <gtk/gtk.h>
#include <libgnome-desktop/gnome-desktop-thumbnail.h>
G_BEGIN_DECLS
#define BG_TYPE_SOURCE (bg_source_get_type ())
G_DECLARE_DERIVABLE_TYPE (BgSource, bg_source, BG, SOURCE, GObject)
struct _BgSourceClass
{
GObjectClass parent_class;
};
GListStore* bg_source_get_liststore (BgSource *source);
G_END_DECLS

View file

@ -0,0 +1,163 @@
/* bg-wallpapers-source.c */
/*
* 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: Thomas Wood <thomas.wood@intel.com>
*
*/
#include "bg-wallpapers-source.h"
#include "cc-background-item.h"
#include "cc-background-xml.h"
#include <cairo-gobject.h>
#include <gio/gio.h>
struct _BgWallpapersSource
{
BgSource parent_instance;
CcBackgroundXml *xml;
};
G_DEFINE_TYPE (BgWallpapersSource, bg_wallpapers_source, BG_TYPE_SOURCE)
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
CcBackgroundItem *item_a;
CcBackgroundItem *item_b;
const char *name_a;
const char *name_b;
item_a = (CcBackgroundItem *) a;
item_b = (CcBackgroundItem *) b;
name_a = cc_background_item_get_name (item_a);
name_b = cc_background_item_get_name (item_b);
if (name_a && strcmp (name_a, "Default Background") == 0)
return -1;
if (name_b && strcmp (name_b, "Default Background") == 0)
return 1;
return strcmp (cc_background_item_get_name (item_a),
cc_background_item_get_name (item_b));
}
static void
load_wallpapers (gchar *key,
CcBackgroundItem *item,
BgWallpapersSource *source)
{
GListStore *store = bg_source_get_liststore (BG_SOURCE (source));
gboolean deleted;
g_object_get (G_OBJECT (item), "is-deleted", &deleted, NULL);
if (deleted)
return;
g_list_store_insert_sorted (store, item, sort_func, NULL);
}
static void
list_load_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GError) error = NULL;
if (!cc_background_xml_load_list_finish (CC_BACKGROUND_XML (source_object), res, &error))
g_warning ("Failed to load background list: %s", error->message);
}
static void
item_added (BgWallpapersSource *self,
CcBackgroundItem *item)
{
load_wallpapers (NULL, item, self);
}
static void
load_default_bg (BgWallpapersSource *self)
{
const char * const *system_data_dirs;
guint i;
/* FIXME We could do this nicer if we had the XML source in GSettings */
system_data_dirs = g_get_system_data_dirs ();
for (i = 0; system_data_dirs[i]; i++) {
g_autofree gchar *filename = NULL;
filename = g_build_filename (system_data_dirs[i],
"gnome-background-properties",
"adwaita.xml",
NULL);
if (cc_background_xml_load_xml (self->xml, filename))
break;
}
}
static void
bg_wallpapers_source_constructed (GObject *object)
{
BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object);
G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->constructed (object);
g_signal_connect_object (G_OBJECT (self->xml), "added",
G_CALLBACK (item_added), self, G_CONNECT_SWAPPED);
/* Try adding the default background first */
load_default_bg (self);
cc_background_xml_load_list_async (self->xml, NULL, list_load_cb, self);
}
static void
bg_wallpapers_source_dispose (GObject *object)
{
BgWallpapersSource *self = BG_WALLPAPERS_SOURCE (object);
g_clear_object (&self->xml);
G_OBJECT_CLASS (bg_wallpapers_source_parent_class)->dispose (object);
}
static void
bg_wallpapers_source_init (BgWallpapersSource *self)
{
self->xml = cc_background_xml_new ();
}
static void
bg_wallpapers_source_class_init (BgWallpapersSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = bg_wallpapers_source_constructed;
object_class->dispose = bg_wallpapers_source_dispose;
}
BgWallpapersSource *
bg_wallpapers_source_new (void)
{
return g_object_new (BG_TYPE_WALLPAPERS_SOURCE, NULL);
}

View file

@ -0,0 +1,34 @@
/* bg-wallpapers-source.h */
/*
* 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: Thomas Wood <thomas.wood@intel.com>
*
*/
#pragma once
#include <gtk/gtk.h>
#include "bg-source.h"
G_BEGIN_DECLS
#define BG_TYPE_WALLPAPERS_SOURCE (bg_wallpapers_source_get_type ())
G_DECLARE_FINAL_TYPE (BgWallpapersSource, bg_wallpapers_source, BG, WALLPAPERS_SOURCE, BgSource)
BgWallpapersSource *bg_wallpapers_source_new (void);
G_END_DECLS

View file

@ -0,0 +1,527 @@
/* cc-background-chooser.c
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "cc-background-chooser"
#include <adwaita.h>
#include <glib/gi18n.h>
#include <libgnome-desktop/gnome-desktop-thumbnail.h>
#include "bg-recent-source.h"
#include "bg-wallpapers-source.h"
#include "cc-background-chooser.h"
#include "cc-background-paintable.h"
#include "cc-background-item.h"
#define THUMBNAIL_WIDTH 144
#define THUMBNAIL_HEIGHT (THUMBNAIL_WIDTH * 3 / 4)
struct _CcBackgroundChooser
{
GtkBox parent;
GtkFlowBox *flowbox;
GtkWidget *recent_box;
GtkFlowBox *recent_flowbox;
gboolean recent_selected;
BgWallpapersSource *wallpapers_source;
BgRecentSource *recent_source;
CcBackgroundItem *active_item;
GnomeDesktopThumbnailFactory *thumbnail_factory;
AdwToastOverlay *toast_overlay;
AdwToast *toast;
GPtrArray *removed_backgrounds;
};
typedef struct {
BgRecentSource *recent_source;
CcBackgroundItem *item;
GtkWidget *parent;
} UndoData;
G_DEFINE_TYPE (CcBackgroundChooser, cc_background_chooser, GTK_TYPE_BOX)
enum
{
PROP_0,
PROP_TOAST_OVERLAY,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
enum
{
BACKGROUND_CHOSEN,
N_SIGNALS,
};
static guint signals [N_SIGNALS];
static void
emit_background_chosen (CcBackgroundChooser *self)
{
g_autoptr(GList) list = NULL;
CcBackgroundItem *item;
GtkFlowBox *flowbox;
flowbox = self->recent_selected ? self->recent_flowbox : self->flowbox;
list = gtk_flow_box_get_selected_children (flowbox);
g_assert (g_list_length (list) == 1);
item = g_object_get_data (list->data, "item");
g_signal_emit (self, signals[BACKGROUND_CHOSEN], 0, item);
}
static void
really_delete_background (gpointer data)
{
UndoData *undo_data = data;
bg_recent_source_remove_item (undo_data->recent_source, undo_data->item);
g_free (undo_data);
}
static void
undo_remove (gpointer data)
{
UndoData *undo_data = data;
gtk_widget_set_visible (undo_data->parent, TRUE);
g_free (undo_data);
}
static void
on_removed_backgrounds_undo (CcBackgroundChooser *self)
{
g_ptr_array_set_free_func (self->removed_backgrounds, undo_remove);
gtk_widget_set_visible (self->recent_box, TRUE);
}
static void
on_removed_backgrounds_dismissed (CcBackgroundChooser *self)
{
self->toast = NULL;
g_clear_pointer (&self->removed_backgrounds, g_ptr_array_unref);
}
static void
on_delete_background_clicked_cb (GtkButton *button,
BgRecentSource *source)
{
GtkWidget *parent;
CcBackgroundChooser *self;
CcBackgroundItem *item;
UndoData *undo_data;
GListStore *store;
parent = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (button)));
g_assert (GTK_IS_FLOW_BOX_CHILD (parent));
item = g_object_get_data (G_OBJECT (parent), "item");
self = g_object_get_data (G_OBJECT (source), "background-chooser");
gtk_widget_set_visible (parent, FALSE);
/* Add background to the array of rows to be handled by the undo toast */
if (!self->removed_backgrounds)
self->removed_backgrounds = g_ptr_array_new_with_free_func (really_delete_background);
undo_data = g_new (UndoData, 1);
undo_data->recent_source = source;
undo_data->item = item;
undo_data->parent = parent;
g_ptr_array_add (self->removed_backgrounds, undo_data);
/* Recent flowbox should be hidden if all backgrounds are to be removed */
store = bg_source_get_liststore (BG_SOURCE (self->recent_source));
if (self->removed_backgrounds->len == g_list_model_get_n_items (G_LIST_MODEL (store)))
gtk_widget_set_visible (self->recent_box, FALSE);
if (!self->toast)
{
self->toast = adw_toast_new (_("Background removed"));
adw_toast_set_button_label (self->toast, _("_Undo"));
g_signal_connect_swapped (self->toast,
"button-clicked",
G_CALLBACK (on_removed_backgrounds_undo),
self);
g_signal_connect_swapped (self->toast,
"dismissed",
G_CALLBACK (on_removed_backgrounds_dismissed),
self);
}
else
{
g_autofree gchar *message = NULL;
/* Translators: %d is the number of backgrounds deleted. */
message = g_strdup_printf (ngettext ("%d background removed",
"%d backgrounds removed",
self->removed_backgrounds->len),
self->removed_backgrounds->len);
adw_toast_set_title (self->toast, message);
g_object_ref (self->toast);
}
adw_toast_overlay_add_toast (self->toast_overlay, self->toast);
}
static GtkWidget*
create_widget_func (gpointer model_item,
gpointer user_data)
{
g_autoptr(CcBackgroundPaintable) paintable = NULL;
CcBackgroundChooser *self;
CcBackgroundItem *item;
GtkWidget *overlay;
GtkWidget *child;
GtkWidget *picture;
GtkWidget *icon;
GtkWidget *check;
GtkWidget *button = NULL;
BgSource *source;
source = BG_SOURCE (user_data);
item = CC_BACKGROUND_ITEM (model_item);
self = g_object_get_data (G_OBJECT (source), "background-chooser");
paintable = cc_background_paintable_new (self->thumbnail_factory,
item,
CC_BACKGROUND_PAINT_LIGHT_DARK,
THUMBNAIL_WIDTH,
THUMBNAIL_HEIGHT,
GTK_WIDGET (self));
picture = gtk_picture_new_for_paintable (GDK_PAINTABLE (paintable));
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
icon = gtk_image_new_from_icon_name ("slideshow-symbolic");
gtk_widget_set_halign (icon, GTK_ALIGN_START);
gtk_widget_set_valign (icon, GTK_ALIGN_END);
gtk_widget_set_visible (icon, cc_background_item_changes_with_time (item));
gtk_widget_add_css_class (icon, "slideshow-icon");
check = gtk_image_new_from_icon_name ("background-selected-symbolic");
gtk_widget_set_halign (check, GTK_ALIGN_END);
gtk_widget_set_valign (check, GTK_ALIGN_END);
gtk_widget_add_css_class (check, "selected-check");
if (BG_IS_RECENT_SOURCE (source))
{
button = gtk_button_new_from_icon_name ("cross-small-symbolic");
gtk_widget_set_halign (button, GTK_ALIGN_END);
gtk_widget_set_valign (button, GTK_ALIGN_START);
gtk_widget_add_css_class (button, "osd");
gtk_widget_add_css_class (button, "circular");
gtk_widget_add_css_class (button, "remove-button");
gtk_widget_set_tooltip_text (GTK_WIDGET (button), _("Remove Background"));
g_signal_connect (button,
"clicked",
G_CALLBACK (on_delete_background_clicked_cb),
source);
}
overlay = gtk_overlay_new ();
gtk_widget_set_overflow (overlay, GTK_OVERFLOW_HIDDEN);
gtk_widget_add_css_class (overlay, "background-thumbnail");
gtk_overlay_set_child (GTK_OVERLAY (overlay), picture);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), icon);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), check);
if (button)
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), button);
child = gtk_flow_box_child_new ();
gtk_widget_set_halign (child, GTK_ALIGN_CENTER);
gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (child), overlay);
gtk_accessible_update_property (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_PROPERTY_LABEL,
cc_background_item_get_name (item),
-1);
g_object_set_data_full (G_OBJECT (child), "item", g_object_ref (item), g_object_unref);
if (self->active_item && cc_background_item_compare (item, self->active_item))
{
gtk_widget_add_css_class (GTK_WIDGET (child), "active-item");
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_CHECKED, TRUE,
-1);
}
else
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_CHECKED, FALSE,
-1);
return child;
}
static void
update_recent_visibility (CcBackgroundChooser *self)
{
GListStore *store;
gboolean has_items;
store = bg_source_get_liststore (BG_SOURCE (self->recent_source));
has_items = g_list_model_get_n_items (G_LIST_MODEL (store)) != 0;
gtk_widget_set_visible (self->recent_box, has_items);
}
static void
setup_flowbox (CcBackgroundChooser *self)
{
GListStore *store;
store = bg_source_get_liststore (BG_SOURCE (self->wallpapers_source));
gtk_flow_box_bind_model (self->flowbox,
G_LIST_MODEL (store),
create_widget_func,
self->wallpapers_source,
NULL);
store = bg_source_get_liststore (BG_SOURCE (self->recent_source));
gtk_flow_box_bind_model (self->recent_flowbox,
G_LIST_MODEL (store),
create_widget_func,
self->recent_source,
NULL);
update_recent_visibility (self);
g_signal_connect_object (store,
"items-changed",
G_CALLBACK (update_recent_visibility),
self,
G_CONNECT_SWAPPED);
}
static void
on_item_activated_cb (CcBackgroundChooser *self,
GtkFlowBoxChild *child,
GtkFlowBox *flowbox)
{
self->recent_selected = flowbox == self->recent_flowbox;
if (self->recent_selected)
gtk_flow_box_unselect_all (self->flowbox);
else
gtk_flow_box_unselect_all (self->recent_flowbox);
emit_background_chosen (self);
}
static void
file_dialog_open_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
CcBackgroundChooser *self = CC_BACKGROUND_CHOOSER (user_data);
GtkFileDialog *file_dialog = GTK_FILE_DIALOG (source_object);
g_autoptr(GListModel) files = NULL;
g_autoptr(GError) error = NULL;
guint i;
files = gtk_file_dialog_open_multiple_finish (file_dialog, res, &error);
if (error != NULL)
{
g_warning ("Failed to pick backgrounds: %s", error->message);
return;
}
for (i = 0; i < g_list_model_get_n_items (files); i++)
{
g_autoptr(GFile) file = g_list_model_get_item (files, i);
g_autofree gchar *filename = g_file_get_path (file);
bg_recent_source_add_file (self->recent_source, filename);
}
}
/* GObject overrides */
static void
cc_background_chooser_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcBackgroundChooser *self = CC_BACKGROUND_CHOOSER (object);
switch (prop_id)
{
case PROP_TOAST_OVERLAY:
self->toast_overlay = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
cc_background_chooser_finalize (GObject *object)
{
CcBackgroundChooser *self = (CcBackgroundChooser *)object;
g_clear_object (&self->recent_source);
g_clear_object (&self->wallpapers_source);
g_clear_object (&self->thumbnail_factory);
G_OBJECT_CLASS (cc_background_chooser_parent_class)->finalize (object);
}
static void
cc_background_chooser_class_init (CcBackgroundChooserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->set_property = cc_background_chooser_set_property;
object_class->finalize = cc_background_chooser_finalize;
properties[PROP_TOAST_OVERLAY] = g_param_spec_object ("toast-overlay", NULL, NULL,
ADW_TYPE_TOAST_OVERLAY,
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
signals[BACKGROUND_CHOSEN] = g_signal_new ("background-chosen",
CC_TYPE_BACKGROUND_CHOOSER,
G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
1,
CC_TYPE_BACKGROUND_ITEM);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/background/cc-background-chooser.ui");
gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, flowbox);
gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_box);
gtk_widget_class_bind_template_child (widget_class, CcBackgroundChooser, recent_flowbox);
gtk_widget_class_bind_template_callback (widget_class, on_item_activated_cb);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
cc_background_chooser_init (CcBackgroundChooser *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->recent_source = bg_recent_source_new ();
self->wallpapers_source = bg_wallpapers_source_new ();
self->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
g_object_set_data (G_OBJECT (self->recent_source), "background-chooser", self);
g_object_set_data (G_OBJECT (self->wallpapers_source), "background-chooser", self);
setup_flowbox (self);
}
void
cc_background_chooser_select_file (CcBackgroundChooser *self)
{
g_autoptr(GFile) pictures_folder = NULL;
GtkFileFilter *filter;
GtkFileDialog *file_dialog;
GtkWindow *toplevel;
GListStore *filters;
g_return_if_fail (CC_IS_BACKGROUND_CHOOSER (self));
toplevel = (GtkWindow*) gtk_widget_get_native (GTK_WIDGET (self));
file_dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (file_dialog, _("Select Picture"));
gtk_file_dialog_set_modal (file_dialog, TRUE);
filter = gtk_file_filter_new ();
gtk_file_filter_add_pixbuf_formats (filter);
filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
g_list_store_append (filters, filter);
gtk_file_dialog_set_filters (file_dialog, G_LIST_MODEL (filters));
pictures_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
gtk_file_dialog_set_initial_folder (file_dialog, pictures_folder);
gtk_file_dialog_open_multiple (file_dialog,
toplevel,
NULL,
file_dialog_open_cb,
self);
}
static void flow_box_set_active_item (GtkFlowBox *flowbox, CcBackgroundItem *active_item)
{
GtkFlowBoxChild *child = NULL;
CcBackgroundItem *item;
int idx = 0;
while ((child = gtk_flow_box_get_child_at_index (flowbox, idx++)))
{
item = g_object_get_data (G_OBJECT (child), "item");
if (cc_background_item_compare (item, active_item))
{
gtk_widget_add_css_class (GTK_WIDGET (child), "active-item");
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_CHECKED, TRUE,
-1);
}
else
{
gtk_widget_remove_css_class (GTK_WIDGET (child), "active-item");
gtk_accessible_update_state (GTK_ACCESSIBLE (child),
GTK_ACCESSIBLE_STATE_CHECKED, FALSE,
-1);
}
}
}
void
cc_background_chooser_set_active_item (CcBackgroundChooser *self, CcBackgroundItem *active_item)
{
g_return_if_fail (CC_IS_BACKGROUND_CHOOSER (self));
g_return_if_fail (CC_IS_BACKGROUND_ITEM (active_item));
self->active_item = active_item;
flow_box_set_active_item (self->flowbox, active_item);
flow_box_set_active_item (self->recent_flowbox, active_item);
}

View file

@ -0,0 +1,35 @@
/* cc-background-chooser.h
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
#include "cc-background-item.h"
G_BEGIN_DECLS
#define CC_TYPE_BACKGROUND_CHOOSER (cc_background_chooser_get_type())
G_DECLARE_FINAL_TYPE (CcBackgroundChooser, cc_background_chooser, CC, BACKGROUND_CHOOSER, GtkBox)
void cc_background_chooser_select_file (CcBackgroundChooser *self);
void cc_background_chooser_set_active_item (CcBackgroundChooser *self,
CcBackgroundItem *active_item);
G_END_DECLS

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="CcBackgroundChooser" parent="GtkBox">
<property name="orientation">vertical</property>
<!-- Recent -->
<child>
<object class="GtkBox" id="recent_box">
<property name="orientation">vertical</property>
<property name="halign">center</property>
<child>
<object class="GtkFlowBox" id="recent_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="column-spacing">12</property>
<property name="row-spacing">12</property>
<property name="homogeneous">True</property>
<property name="halign">center</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">8</property>
<property name="activate-on-single-click">True</property>
<property name="selection-mode">single</property>
<signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="yes" />
<style>
<class name="background-flowbox"/>
</style>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
</object>
</child>
</object>
</child>
<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="column-spacing">12</property>
<property name="row-spacing">12</property>
<property name="homogeneous">True</property>
<property name="halign">center</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">8</property>
<property name="activate-on-single-click">True</property>
<property name="selection-mode">single</property>
<signal name="child-activated" handler="on_item_activated_cb" object="CcBackgroundChooser" swapped="yes" />
<style>
<class name="background-flowbox"/>
</style>
</object>
</child>
</template>
</interface>

File diff suppressed because it is too large Load diff

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