@@ -0,0 +1,35 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[meson.build]
|
||||||
|
indent_size = 2
|
||||||
|
tab_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.{c,h,h.in}]
|
||||||
|
indent_size = 2
|
||||||
|
tab_size = 2
|
||||||
|
indent_style = space
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
[*.css]
|
||||||
|
indent_size = 2
|
||||||
|
tab_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.xml]
|
||||||
|
indent_size = 2
|
||||||
|
tab_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
|
tab_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[NEWS]
|
||||||
|
max_line_length = 72
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
_build
|
||||||
|
debian/.debhelper/
|
||||||
|
debian/debhelper-build-stamp
|
||||||
|
debian/files
|
||||||
|
debian/gir1.2-feedback-0.0.substvars
|
||||||
|
debian/gir1.2-feedback-0.0/
|
||||||
|
debian/feedbackd.debhelper.log
|
||||||
|
debian/feedbackd.substvars
|
||||||
|
debian/feedbackd/
|
||||||
|
debian/libfeedback-0.0-0.substvars
|
||||||
|
debian/libfeedback-0.0-0/
|
||||||
|
debian/libfeedback-dev.substvars
|
||||||
|
debian/libfeedback-dev/
|
||||||
|
debian/tmp/
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
include:
|
||||||
|
- 'https://source.puri.sm/Librem5/librem5-ci/raw/master/librem5-pipeline-definitions.yml'
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- package
|
||||||
|
- test-package
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DEPS: build-essential git
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export DEBIAN_FRONTEND=noninteractive
|
||||||
|
- apt-get -y update
|
||||||
|
- apt-get -y build-dep .
|
||||||
|
- apt-get -y install gcovr
|
||||||
|
|
||||||
|
.tags: &tags
|
||||||
|
tags:
|
||||||
|
- librem5
|
||||||
|
|
||||||
|
# Regular, full build
|
||||||
|
build:native-debian-buster:full:
|
||||||
|
<<: *tags
|
||||||
|
stage: build
|
||||||
|
image: debian:buster
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- _build
|
||||||
|
script:
|
||||||
|
- meson --werror . _build
|
||||||
|
- ninja -C _build
|
||||||
|
|
||||||
|
# Minimal build for e.g. submodule usage
|
||||||
|
build:native-debian-buster:lib:
|
||||||
|
<<: *tags
|
||||||
|
stage: build
|
||||||
|
image: debian:buster
|
||||||
|
script:
|
||||||
|
- meson --werror -Ddaemon=false -Dgtk_doc=false . _build
|
||||||
|
- ninja -C _build
|
||||||
|
|
||||||
|
build:native-debian-bullseye:
|
||||||
|
<<: *tags
|
||||||
|
stage: build
|
||||||
|
image: debian:bullseye
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- _build
|
||||||
|
script:
|
||||||
|
- meson -Db_coverage=true --werror . _build
|
||||||
|
- ninja -C _build
|
||||||
|
|
||||||
|
test:native-debian-bullseye:
|
||||||
|
<<: *tags
|
||||||
|
stage: test
|
||||||
|
image: debian:bullseye
|
||||||
|
dependencies:
|
||||||
|
- build:native-debian-bullseye
|
||||||
|
coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/'
|
||||||
|
script:
|
||||||
|
- dbus-run-session ninja -C _build test coverage
|
||||||
|
|
||||||
|
package:deb-debian-buster:
|
||||||
|
extends: .l5-build-debian-package
|
||||||
|
|
||||||
|
package:deb-debian-buster:arm64:
|
||||||
|
tags:
|
||||||
|
- librem5:arm64
|
||||||
|
extends: .l5-build-debian-package
|
||||||
|
|
||||||
|
autopkgtest-debian-buster-package:
|
||||||
|
dependencies:
|
||||||
|
- package:deb-debian-buster
|
||||||
|
extends: .l5-autopkgtest-debian-package
|
||||||
|
|
||||||
|
lintian-debian-buster-package:
|
||||||
|
dependencies:
|
||||||
|
- package:deb-debian-buster
|
||||||
|
extends: .l5-lintian-debian-package
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
+502
@@ -0,0 +1,502 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 2.1, February 1999
|
||||||
|
|
||||||
|
Copyright (C) 1991, 1999 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.
|
||||||
|
|
||||||
|
[This is the first released version of the Lesser GPL. It also counts
|
||||||
|
as the successor of the GNU Library Public License, version 2, hence
|
||||||
|
the version number 2.1.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
Licenses are intended to guarantee your freedom to share and change
|
||||||
|
free software--to make sure the software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Lesser General Public License, applies to some
|
||||||
|
specially designated software packages--typically libraries--of the
|
||||||
|
Free Software Foundation and other authors who decide to use it. You
|
||||||
|
can use it too, but we suggest you first think carefully about whether
|
||||||
|
this license or the ordinary General Public License is the better
|
||||||
|
strategy to use in any particular case, based on the explanations below.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom of use,
|
||||||
|
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 and use pieces of
|
||||||
|
it in new free programs; and that you are informed that you can do
|
||||||
|
these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
distributors to deny you these rights or to ask you to surrender these
|
||||||
|
rights. These restrictions translate to certain responsibilities for
|
||||||
|
you if you distribute copies of the library or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave
|
||||||
|
you. You must make sure that they, too, receive or can get the source
|
||||||
|
code. If you link other code with the library, you must provide
|
||||||
|
complete object files to the recipients, so that they can relink them
|
||||||
|
with the library after making changes to the library and recompiling
|
||||||
|
it. And you must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
We protect your rights with a two-step method: (1) we copyright the
|
||||||
|
library, and (2) we offer you this license, which gives you legal
|
||||||
|
permission to copy, distribute and/or modify the library.
|
||||||
|
|
||||||
|
To protect each distributor, we want to make it very clear that
|
||||||
|
there is no warranty for the free library. Also, if the library is
|
||||||
|
modified by someone else and passed on, the recipients should know
|
||||||
|
that what they have is not the original version, so that the original
|
||||||
|
author's reputation will not be affected by problems that might be
|
||||||
|
introduced by others.
|
||||||
|
|
||||||
|
Finally, software patents pose a constant threat to the existence of
|
||||||
|
any free program. We wish to make sure that a company cannot
|
||||||
|
effectively restrict the users of a free program by obtaining a
|
||||||
|
restrictive license from a patent holder. Therefore, we insist that
|
||||||
|
any patent license obtained for a version of the library must be
|
||||||
|
consistent with the full freedom of use specified in this license.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the
|
||||||
|
ordinary GNU General Public License. This license, the GNU Lesser
|
||||||
|
General Public License, applies to certain designated libraries, and
|
||||||
|
is quite different from the ordinary General Public License. We use
|
||||||
|
this license for certain libraries in order to permit linking those
|
||||||
|
libraries into non-free programs.
|
||||||
|
|
||||||
|
When a program is linked with a library, whether statically or using
|
||||||
|
a shared library, the combination of the two is legally speaking a
|
||||||
|
combined work, a derivative of the original library. The ordinary
|
||||||
|
General Public License therefore permits such linking only if the
|
||||||
|
entire combination fits its criteria of freedom. The Lesser General
|
||||||
|
Public License permits more lax criteria for linking other code with
|
||||||
|
the library.
|
||||||
|
|
||||||
|
We call this license the "Lesser" General Public License because it
|
||||||
|
does Less to protect the user's freedom than the ordinary General
|
||||||
|
Public License. It also provides other free software developers Less
|
||||||
|
of an advantage over competing non-free programs. These disadvantages
|
||||||
|
are the reason we use the ordinary General Public License for many
|
||||||
|
libraries. However, the Lesser license provides advantages in certain
|
||||||
|
special circumstances.
|
||||||
|
|
||||||
|
For example, on rare occasions, there may be a special need to
|
||||||
|
encourage the widest possible use of a certain library, so that it becomes
|
||||||
|
a de-facto standard. To achieve this, non-free programs must be
|
||||||
|
allowed to use the library. A more frequent case is that a free
|
||||||
|
library does the same job as widely used non-free libraries. In this
|
||||||
|
case, there is little to gain by limiting the free library to free
|
||||||
|
software only, so we use the Lesser General Public License.
|
||||||
|
|
||||||
|
In other cases, permission to use a particular library in non-free
|
||||||
|
programs enables a greater number of people to use a large body of
|
||||||
|
free software. For example, permission to use the GNU C Library in
|
||||||
|
non-free programs enables many more people to use the whole GNU
|
||||||
|
operating system, as well as its variant, the GNU/Linux operating
|
||||||
|
system.
|
||||||
|
|
||||||
|
Although the Lesser General Public License is Less protective of the
|
||||||
|
users' freedom, it does ensure that the user of a program that is
|
||||||
|
linked with the Library has the freedom and the wherewithal to run
|
||||||
|
that program using a modified version of the Library.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow. Pay close attention to the difference between a
|
||||||
|
"work based on the library" and a "work that uses the library". The
|
||||||
|
former contains code derived from the library, whereas the latter must
|
||||||
|
be combined with the library in order to run.
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library or other
|
||||||
|
program which contains a notice placed by the copyright holder or
|
||||||
|
other authorized party saying it may be distributed under the terms of
|
||||||
|
this Lesser General Public License (also called "this License").
|
||||||
|
Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data
|
||||||
|
prepared so as to be conveniently linked with application programs
|
||||||
|
(which use some of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work
|
||||||
|
which has been distributed under these terms. A "work based on the
|
||||||
|
Library" means either the Library or any derivative work under
|
||||||
|
copyright law: that is to say, a work containing the Library or a
|
||||||
|
portion of it, either verbatim or with modifications and/or translated
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is
|
||||||
|
included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For a library, 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 library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running a program using the Library is not restricted, and output from
|
||||||
|
such a program is covered only if its contents constitute a work based
|
||||||
|
on the Library (independent of the use of the Library in a tool for
|
||||||
|
writing it). Whether that is true depends on what the Library does
|
||||||
|
and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's
|
||||||
|
complete 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 distribute a copy of this License along with the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
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 Library or any portion
|
||||||
|
of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no
|
||||||
|
charge to all third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a
|
||||||
|
table of data to be supplied by an application program that uses
|
||||||
|
the facility, other than as an argument passed when the facility
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,
|
||||||
|
in the event an application does not supply such function or
|
||||||
|
table, the facility still operates, and performs whatever part of
|
||||||
|
its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has
|
||||||
|
a purpose that is entirely well-defined independent of the
|
||||||
|
application. Therefore, Subsection 2d requires that any
|
||||||
|
application-supplied function or table used by this function must
|
||||||
|
be optional: if the application does not supply it, the square
|
||||||
|
root function must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Library,
|
||||||
|
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 Library, 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 Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library
|
||||||
|
with the Library (or with a work based on the Library) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||||
|
License instead of this License to a given copy of the Library. To do
|
||||||
|
this, you must alter all the notices that refer to this License, so
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,
|
||||||
|
instead of to this License. (If a newer version than version 2 of the
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
|
that version instead if you wish.) Do not make any other change in
|
||||||
|
these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all
|
||||||
|
subsequent copies and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of
|
||||||
|
the Library into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or
|
||||||
|
derivative of it, under Section 2) in object code or executable form
|
||||||
|
under the terms of Sections 1 and 2 above provided that you 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.
|
||||||
|
|
||||||
|
If distribution of 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 satisfies the requirement to
|
||||||
|
distribute the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the
|
||||||
|
Library, but is designed to work with the Library by being compiled or
|
||||||
|
linked with it, is called a "work that uses the Library". Such a
|
||||||
|
work, in isolation, is not a derivative work of the Library, and
|
||||||
|
therefore falls outside the scope of this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library
|
||||||
|
creates an executable that is a derivative of the Library (because it
|
||||||
|
contains portions of the Library), rather than a "work that uses the
|
||||||
|
library". The executable is therefore covered by this License.
|
||||||
|
Section 6 states terms for distribution of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file
|
||||||
|
that is part of the Library, the object code for the work may be a
|
||||||
|
derivative work of the Library even though the source code is not.
|
||||||
|
Whether this is true is especially significant if the work can be
|
||||||
|
linked without the Library, or if the work is itself a library. The
|
||||||
|
threshold for this to be true is not precisely defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data
|
||||||
|
structure layouts and accessors, and small macros and small inline
|
||||||
|
functions (ten lines or less in length), then the use of the object
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative
|
||||||
|
work. (Executables containing this object code plus portions of the
|
||||||
|
Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may
|
||||||
|
distribute the object code for the work under the terms of Section 6.
|
||||||
|
Any executables containing that work also fall under Section 6,
|
||||||
|
whether or not they are linked directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also combine or
|
||||||
|
link a "work that uses the Library" with the Library to produce a
|
||||||
|
work containing portions of the Library, and distribute that work
|
||||||
|
under terms of your choice, provided that the terms permit
|
||||||
|
modification of the work for the customer's own use and reverse
|
||||||
|
engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the
|
||||||
|
Library is used in it and that the Library and its use are covered by
|
||||||
|
this License. You must supply a copy of this License. If the work
|
||||||
|
during execution displays copyright notices, you must include the
|
||||||
|
copyright notice for the Library among them, as well as a reference
|
||||||
|
directing the user to the copy of this License. Also, you must do one
|
||||||
|
of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding
|
||||||
|
machine-readable source code for the Library including whatever
|
||||||
|
changes were used in the work (which must be distributed under
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked
|
||||||
|
with the Library, with the complete machine-readable "work that
|
||||||
|
uses the Library", as object code and/or source code, so that the
|
||||||
|
user can modify the Library and then relink to produce a modified
|
||||||
|
executable containing the modified Library. (It is understood
|
||||||
|
that the user who changes the contents of definitions files in the
|
||||||
|
Library will not necessarily be able to recompile the application
|
||||||
|
to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (1) uses at run time a
|
||||||
|
copy of the library already present on the user's computer system,
|
||||||
|
rather than copying library functions into the executable, and (2)
|
||||||
|
will operate properly with a modified version of the library, if
|
||||||
|
the user installs one, as long as the modified version is
|
||||||
|
interface-compatible with the version that the work was made with.
|
||||||
|
|
||||||
|
c) Accompany the work with a written offer, valid for at
|
||||||
|
least three years, to give the same user the materials
|
||||||
|
specified in Subsection 6a, above, for a charge no more
|
||||||
|
than the cost of performing this distribution.
|
||||||
|
|
||||||
|
d) If distribution of the work is made by offering access to copy
|
||||||
|
from a designated place, offer equivalent access to copy the above
|
||||||
|
specified materials from the same place.
|
||||||
|
|
||||||
|
e) Verify that the user has already received a copy of these
|
||||||
|
materials or that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the
|
||||||
|
Library" must include any data and utility programs needed for
|
||||||
|
reproducing the executable from it. However, as a special exception,
|
||||||
|
the materials to be 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.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license
|
||||||
|
restrictions of other proprietary libraries that do not normally
|
||||||
|
accompany the operating system. Such a contradiction means you cannot
|
||||||
|
use both them and the Library together in an executable that you
|
||||||
|
distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the
|
||||||
|
Library side-by-side in a single library together with other library
|
||||||
|
facilities not covered by this License, and distribute such a combined
|
||||||
|
library, provided that the separate distribution of the work based on
|
||||||
|
the Library and of the other library facilities is otherwise
|
||||||
|
permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work
|
||||||
|
based on the Library, uncombined with any other library
|
||||||
|
facilities. This must be distributed under the terms of the
|
||||||
|
Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact
|
||||||
|
that part of it is a work based on the Library, and explaining
|
||||||
|
where to find the accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute
|
||||||
|
the Library except as expressly provided under this License. Any
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or
|
||||||
|
distribute the Library 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.
|
||||||
|
|
||||||
|
9. 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 Library or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Library (or any work based on the
|
||||||
|
Library), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the
|
||||||
|
Library), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute, link with or modify the Library
|
||||||
|
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 with
|
||||||
|
this License.
|
||||||
|
|
||||||
|
11. 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 Library at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Library 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 Library.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Library 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.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new
|
||||||
|
versions of the Lesser 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 Library
|
||||||
|
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 Library does not specify a
|
||||||
|
license version number, you may choose any version ever published by
|
||||||
|
the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free
|
||||||
|
programs whose distribution conditions are incompatible with these,
|
||||||
|
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
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "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
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. 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 LIBRARY 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
|
||||||
|
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest
|
||||||
|
possible use to the public, we recommend making it free software that
|
||||||
|
everyone can redistribute and change. You can do so by permitting
|
||||||
|
redistribution under these terms (or, alternatively, under the terms of the
|
||||||
|
ordinary General Public License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; 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.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
|
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1990
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
This is similar in spirit (and heavily based on) to the [Sound naming spec][].
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This specification gives direction on how to name the Event types
|
||||||
|
(triggering feedbacks such as running the haptic motor or blinking an
|
||||||
|
LED) that are available for use by applications, when creating a
|
||||||
|
feedback theme. It does so by laying out a standard naming scheme for
|
||||||
|
Event creation, as well as providing a minimal list of must have
|
||||||
|
Events, and a larger list with many more examples to help with the
|
||||||
|
creation of extended Events for third party applications, with
|
||||||
|
different event types and usage.
|
||||||
|
|
||||||
|
# Context
|
||||||
|
|
||||||
|
The list of default contexts for the feedback theme are:
|
||||||
|
|
||||||
|
- Alerts: Events to alert the user of an action or event which may
|
||||||
|
have a major impact on the system or their current use
|
||||||
|
- Notifications: Events to trigger feedback to notify the user that
|
||||||
|
the system, or their current use case has changed state in some way,
|
||||||
|
e.g. new email arriving
|
||||||
|
- Actions: Event that notify the user on their actions.
|
||||||
|
- Input Event: This triggers feedbacks that give direct response to
|
||||||
|
input events from the user, such as key presses on an on screen
|
||||||
|
keyboard
|
||||||
|
|
||||||
|
# Event naming guides
|
||||||
|
|
||||||
|
Here we define some guidelines for when creating new Event names
|
||||||
|
that extend the standardized list of Event names defined here, in
|
||||||
|
order to provide Events for more specific events and usages.
|
||||||
|
|
||||||
|
Event names are in the en_US.US_ASCII locale. This means that the
|
||||||
|
characters allowed in the Event names must fall within the US-ASCII
|
||||||
|
character set. As a further restriction, all Event names may only
|
||||||
|
contain lowercase letters, numbers, underscore, dash, or period
|
||||||
|
characters. Spaces, colons, slashes, and backslashes are not
|
||||||
|
allowed. Also, sound names must be spelled as they are in the en_US
|
||||||
|
dictionary.
|
||||||
|
|
||||||
|
Events for branded applications should be named the same as the binary
|
||||||
|
executable for the application, prefixed by the string “x-”, to avoid
|
||||||
|
name space clashes with future standardized names. Example:
|
||||||
|
“x-openoffice-foobar”.
|
||||||
|
|
||||||
|
## Standard Event names
|
||||||
|
|
||||||
|
This section describes the standard Event names that should be used
|
||||||
|
by artists when creating themes, and by developers when writing
|
||||||
|
applications which will use the Feedback Theme Specification.
|
||||||
|
|
||||||
|
### Alerts
|
||||||
|
|
||||||
|
- battery-low: The Event used when the battery is low (below 20%, for example).
|
||||||
|
- power-unplug-battery-low: The power cable has been unplugged and the battery level is low.
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
|
||||||
|
- message-new-instant: The event used when a new IM is received.
|
||||||
|
- message-new-sms: The event used when a new sms is received.
|
||||||
|
- message-new-email: The event used when a new email is received.
|
||||||
|
- message-missed-email: The event used when an email was received but not seen by the user.
|
||||||
|
- message-missed-instant: The event used when a instant message was received but not seen by the user.
|
||||||
|
- message-missed-sms: The event used when a sms message was received but not seen by the user.
|
||||||
|
- phone-incoming-call: This Event is used when a phone/voip call is coming in.
|
||||||
|
- phone-missed-call: This Event is used when a phone/voip call is was incoming but not answered.
|
||||||
|
- battery-caution: The event used when the battery is nearing exhaustion (below 40%, for example)
|
||||||
|
- battery-full: The event used when the battery is fully loaded up.
|
||||||
|
- device-added: The event used when a device has become available to the desktop, i.e. due to USB plugging.
|
||||||
|
- power-plug: The power cable has been plugged in.
|
||||||
|
- power-unplug: The power cable has been unplugged.
|
||||||
|
- alarm-clock-elapsed: A user configured alarm elapsed.
|
||||||
|
|
||||||
|
### Actions
|
||||||
|
|
||||||
|
- message-sent-instant: The sound used when a new IM is sent.
|
||||||
|
- bell-terminal: The sound to use as a terminal bell.
|
||||||
|
- theme-demo: A event that should be played for demoing this theme. Usually
|
||||||
|
this should just be an alias for a very representative sound (such as
|
||||||
|
a incoming phone call) of a theme that would work nicely as a demo event for
|
||||||
|
a theme in the theme selector dialog.
|
||||||
|
|
||||||
|
### Input Event
|
||||||
|
|
||||||
|
- button-pressed: The Event used when a button is pressed.
|
||||||
|
- window-close: The sound used when an existing window is closed.
|
||||||
|
|
||||||
|
[Sound naming spec]: http://0pointer.de/public/sound-naming-spec.html
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
A feedback theme defines the kind of feedback that events (as
|
||||||
|
described in the [Event naming spec][]) will trigger.
|
||||||
|
|
||||||
|
The feedback daemon is responsible to select an appropriate feedback
|
||||||
|
theme for a given device.
|
||||||
|
|
||||||
|
# Definitions
|
||||||
|
|
||||||
|
- Feedback: A feedback is something that notifies the user that
|
||||||
|
s.th. happened (e.g. a played sound, the vibration of a haptic
|
||||||
|
motor or led blinking).
|
||||||
|
|
||||||
|
- Feedback theme: A feedback theme is a set of feedbacks grouped by
|
||||||
|
profiles. Each feedback is mapped to a single event.
|
||||||
|
|
||||||
|
- Feedback Profile: A feedback profile groups feedbacks by
|
||||||
|
"noisiness". The currently defined profiles names are
|
||||||
|
*full*, *quiet* and *silent*. With *full* being the noisiest
|
||||||
|
profile.
|
||||||
|
|
||||||
|
- Event: What the user should be notified about. The event
|
||||||
|
names are built according to the [Event naming spec][].
|
||||||
|
|
||||||
|
# Implementation
|
||||||
|
|
||||||
|
When an application triggers feedback for an event in the feedback
|
||||||
|
daemon the daemon selects the provided feedback like this:
|
||||||
|
|
||||||
|
1. feedbacks from a nosier profiles like the currently selected are
|
||||||
|
ignored
|
||||||
|
|
||||||
|
2. All other feedbacks for this event are selected and run to provide
|
||||||
|
feedback to the user
|
||||||
|
|
||||||
|
With the above a feedback theme in YAML format could look like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
full:
|
||||||
|
- event-name: phone-incoming-call
|
||||||
|
type: Sound
|
||||||
|
effect: phone-incoing-call
|
||||||
|
- event-name: message-new-sms
|
||||||
|
...
|
||||||
|
quiet:
|
||||||
|
- event-name: phone-incoming-call
|
||||||
|
type: Vibra
|
||||||
|
duration: 0.5s
|
||||||
|
- event-name: message-new-sms
|
||||||
|
...
|
||||||
|
silent:
|
||||||
|
- event-name: phone-incoming-call
|
||||||
|
type: Led
|
||||||
|
location: Front
|
||||||
|
Color: Green
|
||||||
|
Interval: 0.2s
|
||||||
|
- event-name: message-new-sms
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
At the time of writing the theme format is daemon dependent. E.g. feedbackd
|
||||||
|
uses a format [similar to the above in JSON](./data/default.json).
|
||||||
|
|
||||||
|
|
||||||
|
# Recommendations
|
||||||
|
|
||||||
|
- The silent theme shoud not produce any audible feedback. This includes
|
||||||
|
the buzzing of haptic motors.
|
||||||
|
- The quiet theme should not play any sounds. Haptic motors and LEDs can
|
||||||
|
be used.
|
||||||
|
- The full feedback theme can use any available feedback mechanisms
|
||||||
|
|
||||||
|
[Event naming spec]: ./Event-naming-spec-0.0.0.md
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
Haptic/visual/audio feedback for GNOME
|
||||||
|
======================================
|
||||||
|
[](https://source.puri.sm/guido.gunther/feedbackd/commits/master)
|
||||||
|
|
||||||
|
feedbackd provides a DBus daemon (feedbackd) to act on events to provide
|
||||||
|
haptic, visual and audio feedback. It offers a library (libfeedback) and
|
||||||
|
GObject introspection bindings to ease using it from applications.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
feedbackd is licensed under the GPLv3+ while the libfeedback library is
|
||||||
|
licensed under LGPL 2.1+.
|
||||||
|
|
||||||
|
## Getting the source
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://source.puri.sm/Librem5/feedbackd
|
||||||
|
cd feedbackd
|
||||||
|
```
|
||||||
|
|
||||||
|
The master branch has the current development version.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
On a Debian based system run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get -y install build-essential
|
||||||
|
sudo apt-get -y build-dep .
|
||||||
|
```
|
||||||
|
|
||||||
|
For an explicit list of dependencies check the `Build-Depends` entry in the
|
||||||
|
[debian/control][] file.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
We use the meson (and thereby Ninja) build system for feedbackd. The quickest
|
||||||
|
way to get going is to do the following:
|
||||||
|
|
||||||
|
meson . _build
|
||||||
|
ninja -C _build
|
||||||
|
ninja -C _build test
|
||||||
|
ninja -C _build install
|
||||||
|
|
||||||
|
## Running
|
||||||
|
### Running from the source tree
|
||||||
|
To run the daemon use
|
||||||
|
|
||||||
|
```sh
|
||||||
|
_build/run
|
||||||
|
```
|
||||||
|
You can introspect and get the current theme with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gdbus introspect --session --dest org.sigxcpu.Feedback --object-path /org/sigxcpu/Feedback
|
||||||
|
```
|
||||||
|
|
||||||
|
and to request feedback for an event
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gdbus call --session --dest org.sigxcpu.Feedback --object-path /org/sigxcpu/Feedback --method org.sigxcpu.Feedback.Feedback 'my.app.id' 'phone-incoming-call' '[]' 0
|
||||||
|
```
|
||||||
|
|
||||||
|
See `examples/` for a simple python example using GObject introspection.
|
||||||
|
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
We're using a [event naming spec](./Event-naming-spec-0.0.0.md)
|
||||||
|
similar to http://0pointer.de/public/sound-naming-spec.html to name
|
||||||
|
events. This will allow us to act as a system sound library so
|
||||||
|
applications only need to call into this library and things like
|
||||||
|
the quiet and silent profile work out of the box.
|
||||||
|
|
||||||
|
## Feedback theme
|
||||||
|
Events are then mapped to a specific type of feedback (sound, led, vibra) via a
|
||||||
|
device specific theme (since devices have different capabilities).
|
||||||
|
|
||||||
|
There's currently only a single hardcoded theme named `default`. The currently
|
||||||
|
available feedback types are:
|
||||||
|
|
||||||
|
- Sound (an audible sound from the sound naming spec)
|
||||||
|
- VibraRumble: haptic motor rumbling
|
||||||
|
- VibraPeriodic: periodic feedback from the haptic motor
|
||||||
|
|
||||||
|
You can check the feedback theme and the classes (prefixed with Fbd)
|
||||||
|
for available properties. Note that the feedback theme API (including
|
||||||
|
the theme file format) is not stable but considered internal to the
|
||||||
|
daemon.
|
||||||
|
|
||||||
|
## Profiles
|
||||||
|
The profile determines which parts of the theme are in use:
|
||||||
|
|
||||||
|
- `full`: Use conigured events form the `full`, `quiet` and `silent` parts of
|
||||||
|
the feedback them.
|
||||||
|
- `quiet`: Use `quiet` and `silent` part from of the feedback theme. This usually
|
||||||
|
means no audio feedback.
|
||||||
|
- `silent`: Only use the `silent` part from the feedback theme. This usually means
|
||||||
|
to not use audio or vibra.
|
||||||
|
|
||||||
|
It can be set via a GSetting
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gsettings set org.sigxcpu.feedbackd profile full
|
||||||
|
```
|
||||||
|
## fbdcli
|
||||||
|
|
||||||
|
`fbdcli` can be used to trigger feedback for different events. Here are some examples:
|
||||||
|
|
||||||
|
### Phone call
|
||||||
|
Run feedbacks for event `phone-incoming-call` until explicitly stopped:
|
||||||
|
|
||||||
|
```
|
||||||
|
_build/cli/fbcli -t 0 -E phone-incoming-call
|
||||||
|
```
|
||||||
|
|
||||||
|
### New instant message
|
||||||
|
Run feedbacks for event `message-new-instant` just once:
|
||||||
|
|
||||||
|
```
|
||||||
|
_build/cli/fbcli -t -1 -E message-new-instant
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alarm clock
|
||||||
|
Run feedbacks for event `message-new-instant` for 10 seconds:
|
||||||
|
|
||||||
|
```
|
||||||
|
_build/cli/fbcli -t 10 -E alarm-clock-elapsed
|
||||||
|
```
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
- [Libfeedback API](https://honk.sigxcpu.org/projects/feedbackd/doc/)
|
||||||
|
- [Event naming spec draft](./Event-naming-spec-0.0.0.md)
|
||||||
|
- [Feedback-theme-spec draft](./Feedback-theme-spec-0.0.0.md)
|
||||||
|
|
||||||
|
[debian/control]: ./debian/control#L5
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
#define LIBFEEDBACK_USE_UNSTABLE_API
|
||||||
|
#include "libfeedback.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#define DEFAULT_EVENT "phone-incoming-call"
|
||||||
|
|
||||||
|
static GMainLoop *loop;
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_watch_expired (gpointer unused)
|
||||||
|
{
|
||||||
|
g_warning ("Watch expired waiting for all feedbacks to finish");
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_feedback_ended (LfbEvent *event, int* data)
|
||||||
|
{
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (event));
|
||||||
|
|
||||||
|
g_debug ("Feedback ended for event");
|
||||||
|
*data = TRUE;
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_user_input (GIOChannel *channel, GIOCondition cond, LfbEvent *event)
|
||||||
|
{
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
|
||||||
|
if (cond == G_IO_IN) {
|
||||||
|
g_print ("Ending feedback\n");
|
||||||
|
if (!lfb_event_end_feedback (event, &err))
|
||||||
|
g_warning("Failed to end feedback: %s", err->message);
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
trigger_event(const char *name, gint timeout)
|
||||||
|
{
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
g_autoptr(LfbEvent) event = NULL;
|
||||||
|
g_autoptr(GIOChannel) input = NULL;
|
||||||
|
int success = FALSE;
|
||||||
|
|
||||||
|
g_print("Triggering feedback for event '%s'\n", name);
|
||||||
|
event = lfb_event_new (name);
|
||||||
|
lfb_event_set_timeout (event, timeout);
|
||||||
|
|
||||||
|
g_signal_connect (event, "feedback-ended", (GCallback)on_feedback_ended, &success);
|
||||||
|
if (!lfb_event_trigger_feedback (event, &err)) {
|
||||||
|
g_print("Failed to report event: %s\n", err->message);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
input = g_io_channel_unix_new (STDIN_FILENO);
|
||||||
|
g_io_add_watch (input, G_IO_IN, (GIOFunc)on_user_input, event);
|
||||||
|
g_print ("Press <RETURN> to end feedback right away.\n");
|
||||||
|
|
||||||
|
loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
g_main_loop_run (loop);
|
||||||
|
g_main_loop_unref (loop);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_profile_changed (LfbGdbusFeedback *proxy, GParamSpec *psepc, gpointer unused)
|
||||||
|
{
|
||||||
|
g_print("Set feedback profile to: '%s'\n",
|
||||||
|
lfb_get_feedback_profile());
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
set_profile (const gchar *profile)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
const gchar *current;
|
||||||
|
|
||||||
|
current = lfb_get_feedback_profile();
|
||||||
|
g_debug("Current profile is %s", current);
|
||||||
|
if (!g_strcmp0 (current, profile)) {
|
||||||
|
g_print ("Profile is already set to %s\n", profile);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug("Setting profile to %s", profile);
|
||||||
|
proxy = lfb_get_proxy ();
|
||||||
|
|
||||||
|
/* Set profile and wait until we got notified about the profile change */
|
||||||
|
loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
lfb_set_feedback_profile(profile);
|
||||||
|
g_signal_connect (proxy, "notify::profile", (GCallback)on_profile_changed, NULL);
|
||||||
|
g_main_loop_run (loop);
|
||||||
|
g_print("Current feedback profile is: '%s'\n",
|
||||||
|
lfb_get_feedback_profile());
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[0])
|
||||||
|
{
|
||||||
|
g_autoptr(GOptionContext) opt_context = NULL;
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
g_autofree gchar *profile = NULL;
|
||||||
|
const char *name = NULL;
|
||||||
|
gboolean success;
|
||||||
|
int watch = 30;
|
||||||
|
int timeout = -1;
|
||||||
|
const GOptionEntry options [] = {
|
||||||
|
{"event", 'E', 0, G_OPTION_ARG_STRING, &name,
|
||||||
|
"Event name. (default: " DEFAULT_EVENT ").", NULL},
|
||||||
|
{"timeout", 't', 0, G_OPTION_ARG_INT, &timeout,
|
||||||
|
"Run feedback for timeout seconds", NULL},
|
||||||
|
{"profile", 'P', 0, G_OPTION_ARG_STRING, &profile,
|
||||||
|
"Profile name to set" , NULL},
|
||||||
|
{"watch", 'w', 0, G_OPTION_ARG_INT, &watch,
|
||||||
|
"How long to watch for feedback longest", NULL},
|
||||||
|
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
setlocale (LC_ALL, "");
|
||||||
|
opt_context = g_option_context_new ("- A cli for feedbackd");
|
||||||
|
g_option_context_add_main_entries (opt_context, options, NULL);
|
||||||
|
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
|
||||||
|
g_warning ("%s", err->message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lfb_init("org.sigxcpu.fbcli", &err)) {
|
||||||
|
g_print("Failed to init libfeedback: %s\n", err->message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
name = g_strdup (DEFAULT_EVENT);
|
||||||
|
|
||||||
|
g_timeout_add_seconds (watch, (GSourceFunc)on_watch_expired, NULL);
|
||||||
|
if (profile)
|
||||||
|
success = set_profile (profile);
|
||||||
|
else
|
||||||
|
success = trigger_event (name, timeout);
|
||||||
|
|
||||||
|
lfb_uninit ();
|
||||||
|
return !success;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
fbcli_sources = [
|
||||||
|
'fbcli.c',
|
||||||
|
]
|
||||||
|
|
||||||
|
fbcli_deps = [
|
||||||
|
libfeedback_dep,
|
||||||
|
gio,
|
||||||
|
gio_unix,
|
||||||
|
]
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'fbcli',
|
||||||
|
fbcli_sources,
|
||||||
|
dependencies: fbcli_deps,
|
||||||
|
install: true,
|
||||||
|
)
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"name" : "default",
|
||||||
|
"profiles" : [
|
||||||
|
{
|
||||||
|
"name" : "full",
|
||||||
|
"feedbacks" : [
|
||||||
|
{
|
||||||
|
"event-name" : "phone-incoming-call",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "phone-incoming-call"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-instant",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "message-new-instant"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-sms",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "message-new-instant"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-email",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "message-new-email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "alarm-clock-elapsed",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "alarm-clock-elapsed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "theme-demo",
|
||||||
|
"type" : "Sound",
|
||||||
|
"effect" : "theme-demo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "quiet",
|
||||||
|
"feedbacks" : [
|
||||||
|
{
|
||||||
|
"event-name" : "phone-incoming-call",
|
||||||
|
"type" : "VibraPeriodic",
|
||||||
|
"duration" : 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "phone-missed-call",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-instant",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-sms",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 500,
|
||||||
|
"count" : 2,
|
||||||
|
"pause" : 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-new-email",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 750,
|
||||||
|
"count" : 3,
|
||||||
|
"pause" : 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-sent-instant",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "bell-terminal",
|
||||||
|
"type" : "VibraRumble",
|
||||||
|
"duration" : 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "alarm-clock-elapsed",
|
||||||
|
"type" : "VibraPeriodic",
|
||||||
|
"magnitude" : 32767,
|
||||||
|
"duration" : 10000,
|
||||||
|
"fade-in-time" : 10000,
|
||||||
|
"fade-in-level": 4096
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "teme-demo",
|
||||||
|
"type" : "VibraPeriodic",
|
||||||
|
"magnitude" : 32767,
|
||||||
|
"duration" : 5000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "silent",
|
||||||
|
"feedbacks" : [
|
||||||
|
{
|
||||||
|
"event-name" : "phone-missed-call",
|
||||||
|
"type" : "Dummy",
|
||||||
|
"duration" : 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-missed-email",
|
||||||
|
"type" : "Dummy",
|
||||||
|
"duration" : 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-missed-instant",
|
||||||
|
"type" : "Dummy",
|
||||||
|
"duration" : 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "message-missed-sms",
|
||||||
|
"type" : "Dummy",
|
||||||
|
"duration" : 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event-name" : "battery-caution",
|
||||||
|
"type" : "Dummy",
|
||||||
|
"duration" : 10000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# DBus server interface
|
||||||
|
prefix = 'org.sigxcpu.'
|
||||||
|
namespace = 'LfbGdbus'
|
||||||
|
|
||||||
|
generated_dbus_sources = []
|
||||||
|
|
||||||
|
dbus_interfaces = [ 'org.sigxcpu.Feedback.xml' ]
|
||||||
|
|
||||||
|
generated_dbus_sources += gnome.gdbus_codegen('lfb-gdbus',
|
||||||
|
sources : dbus_interfaces,
|
||||||
|
object_manager : false,
|
||||||
|
docbook : 'libfeedback',
|
||||||
|
interface_prefix : prefix,
|
||||||
|
install_header : true,
|
||||||
|
install_dir : join_paths(get_option('includedir'), libname),
|
||||||
|
namespace : namespace)
|
||||||
|
|
||||||
|
dbus_header_dir = meson.current_build_dir()
|
||||||
|
dbus_inc = include_directories('.')
|
||||||
|
install_data(dbus_interfaces, install_dir: ifacedir)
|
||||||
|
|
||||||
|
schemas = ['org.sigxcpu.feedbackd.gschema.xml']
|
||||||
|
compiled = gnome.compile_schemas(
|
||||||
|
build_by_default: true
|
||||||
|
)
|
||||||
|
|
||||||
|
install_data(
|
||||||
|
schemas,
|
||||||
|
install_dir: 'share/glib-2.0/schemas'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if get_option('daemon')
|
||||||
|
|
||||||
|
service_data = configuration_data()
|
||||||
|
service_data.set('libexecdir', libexecdir)
|
||||||
|
service_file = 'org.sigxcpu.Feedback.service'
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
input : service_file + '.in',
|
||||||
|
output : service_file,
|
||||||
|
configuration : service_data,
|
||||||
|
install : true,
|
||||||
|
install_dir: servicedir,
|
||||||
|
)
|
||||||
|
|
||||||
|
theme_json = ['default.json']
|
||||||
|
install_data(
|
||||||
|
theme_json,
|
||||||
|
install_dir: feedbackd_theme_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
if json_glib_validate.found()
|
||||||
|
jsons = []
|
||||||
|
foreach theme : theme_json
|
||||||
|
jsons += (join_paths(meson.current_source_dir(), theme_json))
|
||||||
|
endforeach
|
||||||
|
custom_target('validate-json',
|
||||||
|
build_by_default: true,
|
||||||
|
input: theme_json,
|
||||||
|
output: 'json_validate_stamp',
|
||||||
|
command: [json_glib_validate.path()] + jsons)
|
||||||
|
endif
|
||||||
|
|
||||||
|
endif # daemon
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[D-BUS Service]
|
||||||
|
Name=org.sigxcpu.Feedback
|
||||||
|
Exec=@libexecdir@/feedbackd
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<node>
|
||||||
|
<!-- org.sigxcpu.Feedback
|
||||||
|
@short_description: haptic/audio/visual feedback interface
|
||||||
|
|
||||||
|
This D-Bus interface is used to get the current feedback theme
|
||||||
|
and to give feedback on events.
|
||||||
|
-->
|
||||||
|
<interface name="org.sigxcpu.Feedback">
|
||||||
|
<!--
|
||||||
|
Profile: The currently used profile.
|
||||||
|
|
||||||
|
The currently used feedback profile name. Applications should
|
||||||
|
usually not change this value but leave.
|
||||||
|
-->
|
||||||
|
<property name="Profile" type="s" access="readwrite" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
TriggerFeedback:
|
||||||
|
@app_id: The application id usually in reverse "reverse DNS" format
|
||||||
|
@event: The event name from the Event naming spec
|
||||||
|
@hints: Additional hints (e.g. to prefer haptic feedback over audio feedback on button press events, currently unused)
|
||||||
|
@timeout: When the feedbacks for this event should end latest. The special values '-1' (just run each feedack once) and '0' (endless loop) are also supported.
|
||||||
|
@id: Event id for future reference
|
||||||
|
|
||||||
|
Give user feedback for an event by triggering feedbacks
|
||||||
|
defined in the daemon. The method call returns an event id
|
||||||
|
that can be used to later on to e.g. cancel the triggered
|
||||||
|
feedbacks early.
|
||||||
|
|
||||||
|
Depending on the event, theme and profile several forms of
|
||||||
|
feedback will be triggered such as an audio ring tone and a
|
||||||
|
haptic motor.
|
||||||
|
-->
|
||||||
|
<method name="TriggerFeedback">
|
||||||
|
<arg direction="in" name="app_id" type="s"/>
|
||||||
|
<arg direction="in" name="event" type="s"/>
|
||||||
|
<arg direction="in" name="hints" type="a{sv}"/>
|
||||||
|
<arg direction="in" name="timeout" type="i"/>
|
||||||
|
<arg direction="out" name="id" type="u"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
EndFeedback:
|
||||||
|
@id: The id of the event
|
||||||
|
|
||||||
|
End all feedbacks triggered by the event with the given id.
|
||||||
|
-->
|
||||||
|
<method name="EndFeedback">
|
||||||
|
<arg direction="in" name="id" type="u"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
FeedbackEnded:
|
||||||
|
@id: The id of the event
|
||||||
|
@reason: The reason why feedback was ended (currently unused).
|
||||||
|
|
||||||
|
Emitted when all feedbacks for an event have ended.
|
||||||
|
-->
|
||||||
|
<signal name="FeedbackEnded">
|
||||||
|
<arg name="id" type="u"/>
|
||||||
|
<arg name="reason" type="u"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
</node>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<schemalist>
|
||||||
|
<schema id="org.sigxcpu.feedbackd"
|
||||||
|
path="/org/sigxcpu/feedbackd/">
|
||||||
|
<key name="profile" type="s">
|
||||||
|
<choices>
|
||||||
|
<choice value='full'/>
|
||||||
|
<choice value='quiet'/>
|
||||||
|
<choice value='silent'/>
|
||||||
|
</choices>
|
||||||
|
<default>'full'</default>
|
||||||
|
<summary>Feedack profile to use</summary>
|
||||||
|
<description>
|
||||||
|
The feedback profile determines the audio and haptical feedback
|
||||||
|
level given on events to the user.
|
||||||
|
</description>
|
||||||
|
</key>
|
||||||
|
</schema>
|
||||||
|
</schemalist>
|
||||||
Vendored
+29
@@ -0,0 +1,29 @@
|
|||||||
|
This package is maintained with git-buildpackage(1). It follows DEP-14
|
||||||
|
for branch naming (e.g. using debian/sid for the current version
|
||||||
|
in Debian unstable).
|
||||||
|
|
||||||
|
It uses pristine-tar(1) to store enough information in git to generate
|
||||||
|
bit identical tarballs when building the package without having
|
||||||
|
downloaded an upstream tarball first.
|
||||||
|
|
||||||
|
When working with patches it is recommended to use "gbp pq import" to
|
||||||
|
import the patches, modify the source and then use "gbp pq export
|
||||||
|
--commit" to commit the modifications.
|
||||||
|
|
||||||
|
The changelog is generated using "gbp dch" so if you submit any
|
||||||
|
changes don't bother to add changelog entries but rather provide
|
||||||
|
a nice git commit message that can then end up in the changelog.
|
||||||
|
|
||||||
|
It is recommended to build the package with pbuilder using:
|
||||||
|
|
||||||
|
gbp buildpackage --git-pbuilder
|
||||||
|
|
||||||
|
For information on how to set up a pbuilder environment see the
|
||||||
|
git-pbuilder(1) manpage. In short:
|
||||||
|
|
||||||
|
DIST=sid git-pbuilder create
|
||||||
|
gbp clone <project-url>
|
||||||
|
cd <project>
|
||||||
|
gbp buildpackage --git-pbuilder
|
||||||
|
|
||||||
|
-- Guido Günther <agx@sigxcpu.org>, Wed, 2 Dec 2015 18:51:15 +0100
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
feedbackd (0.0.0+git20200214) amber-phone; urgency=medium
|
||||||
|
|
||||||
|
* Initial git snapshot
|
||||||
|
|
||||||
|
-- Guido Günther <agx@sigxcpu.org> Wed, 16 Oct 2019 21:22:33 +0200
|
||||||
Vendored
+74
@@ -0,0 +1,74 @@
|
|||||||
|
Source: feedbackd
|
||||||
|
Section: x11
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Guido Günther <agx@sigxcpu.org>
|
||||||
|
Build-Depends:
|
||||||
|
dbus,
|
||||||
|
debhelper-compat (= 12),
|
||||||
|
dh-sequence-gir,
|
||||||
|
gtk-doc-tools,
|
||||||
|
libgirepository1.0-dev,
|
||||||
|
libglib2.0-dev,
|
||||||
|
libgudev-1.0-dev (>= 232),
|
||||||
|
libgsound-dev,
|
||||||
|
libjson-glib-dev,
|
||||||
|
meson,
|
||||||
|
pkg-config,
|
||||||
|
valac,
|
||||||
|
Standards-Version: 4.4.0
|
||||||
|
Homepage: https://source.puri.sm/Librem5/feedbackd
|
||||||
|
|
||||||
|
Package: feedbackd
|
||||||
|
Architecture: any
|
||||||
|
Depends:
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends},
|
||||||
|
sound-theme-freedesktop,
|
||||||
|
Description: DBus service for haptic/visual/audio feedback
|
||||||
|
Feedbackd is a DBus activated daemon that provides haptic/
|
||||||
|
visual/audio feedback based on events.
|
||||||
|
.
|
||||||
|
This package contains the daemon.
|
||||||
|
|
||||||
|
Package: libfeedback-0.0-0
|
||||||
|
Architecture: any
|
||||||
|
Section: libs
|
||||||
|
Depends:
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends},
|
||||||
|
Description: DBus service for haptic/visual/audio feedback
|
||||||
|
Feedbackd is a DBus activated daemon that provides haptic/
|
||||||
|
visual/audio feedback based on events.
|
||||||
|
.
|
||||||
|
This package contains the shared libraries for applications interfacing with
|
||||||
|
Feedbackd and the API documentation.
|
||||||
|
|
||||||
|
Package: libfeedback-dev
|
||||||
|
Architecture: any
|
||||||
|
Section: libdevel
|
||||||
|
Depends:
|
||||||
|
gir1.2-feedback-0.0 (= ${binary:Version}),
|
||||||
|
libfeedback-0.0-0 (= ${binary:Version}),
|
||||||
|
libglib2.0-dev,
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends},
|
||||||
|
Description: DBus service for haptic/visual/audio feedback
|
||||||
|
Feedbackd is a DBus activated daemon that provides haptic/
|
||||||
|
visual/audio feedback based on events.
|
||||||
|
.
|
||||||
|
This package contains development files and Vala bindings to use when writing
|
||||||
|
applications that interface with GThd.
|
||||||
|
|
||||||
|
Package: gir1.2-feedback-0.0
|
||||||
|
Architecture: any
|
||||||
|
Section: introspection
|
||||||
|
Depends:
|
||||||
|
${gir:Depends},
|
||||||
|
${misc:Depends},
|
||||||
|
${shlibs:Depends},
|
||||||
|
Description: GObject introspection data for libfeedback
|
||||||
|
Feedbackd is a DBus activated daemon that provides haptic/
|
||||||
|
visual/audio feedback based on events.
|
||||||
|
.
|
||||||
|
This package contains introspection data for the libfeedback library.
|
||||||
|
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: feedbackd
|
||||||
|
Source: https://source.puri.sm/Librem5/feedbackd
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2020 Purism SPC
|
||||||
|
License: GPL-3+
|
||||||
|
This package 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 package 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 <https://www.gnu.org/licenses/>
|
||||||
|
.
|
||||||
|
On Debian systems, the complete text of the GNU General
|
||||||
|
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||||
|
|
||||||
|
Files: libfeedback/*
|
||||||
|
Copyright: 2020 Purism SPC
|
||||||
|
License: LGPL-2.1+
|
||||||
|
This package is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
|
This package 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 Lesser General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
.
|
||||||
|
On Debian systems, the complete text of the GNU Lesser General
|
||||||
|
Public License version 3 can be found in "/usr/share/common-licenses/LGPL-2.1".
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
/usr/bin/
|
||||||
|
/usr/libexec/
|
||||||
|
/usr/share/dbus-1/
|
||||||
|
/usr/share/feedbackd/
|
||||||
|
/usr/share/glib-2.0/schemas
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
#
|
||||||
|
# This file is part of feedbackd.
|
||||||
|
|
||||||
|
ACTION=="remove", GOTO="feedbackd_end"
|
||||||
|
|
||||||
|
SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT}=="1", ENV{ID_PATH}=="platform-vibrator", TAG+="uaccess", ENV{FEEDBACKD_TYPE}="vibra"
|
||||||
|
|
||||||
|
LABEL="feedbackd_end"
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
debian-branch = master
|
||||||
|
debian-tag = v%(version)s
|
||||||
|
debian-tag-msg = %(pkg)s v%(version)s
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/*/girepository-1.0
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/*/libfeedback-*.so.0
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/usr/include
|
||||||
|
/usr/share/gir-1.0/
|
||||||
|
/usr/share/gtk-doc/
|
||||||
|
/usr/share/vala/
|
||||||
|
/usr/lib/*/libfeedback-*.so
|
||||||
|
/usr/lib/*/pkgconfig/libfeedback-*.pc
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --builddirectory=_build
|
||||||
|
|
||||||
|
override_dh_auto_configure:
|
||||||
|
dh_auto_configure -- -Dgtk_doc=true
|
||||||
|
|
||||||
|
override_dh_installudev:
|
||||||
|
# Make sure all the symlink rules in 60 ran:
|
||||||
|
dh_installudev --priority=90
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
|
||||||
|
dbus-run-session ninja -C _build test
|
||||||
|
endif
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
CFLAGS=$(shell pkg-config --cflags libfeedback-0.0)
|
||||||
|
LIBS=$(shell pkg-config --libs libfeedback-0.0)
|
||||||
|
|
||||||
|
a.out: debian/tests/build-test.c
|
||||||
|
gcc $(CFLAGS) $< $(LIBS)
|
||||||
|
@echo "Build test of $< succeeded"
|
||||||
|
@rm -f a.out
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#define LIBFEEDBACK_USE_UNSTABLE_API
|
||||||
|
#include <libfeedback.h>
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc,
|
||||||
|
char **argv)
|
||||||
|
{
|
||||||
|
lfb_init("org.sigxcpu.autopkgtest1", NULL);
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
Tests: build-test
|
||||||
|
Depends:
|
||||||
|
build-essential,
|
||||||
|
libfeedback-dev,
|
||||||
|
pkg-config,
|
||||||
|
Restrictions: allow-stderr, superficial
|
||||||
|
|
||||||
|
Test-Command: python3 -c 'import gi; from gi.repository import Lfb'
|
||||||
|
Depends:
|
||||||
|
gir1.2-feedback-0.0,
|
||||||
|
python3,
|
||||||
|
python3-gi,
|
||||||
|
Restrictions: allow-stderr, superficial
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||||
|
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
|
||||||
|
<!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
|
||||||
|
<!ENTITY % gtkdocentities SYSTEM "xml/gtkdocentities.ent">
|
||||||
|
%gtkdocentities;
|
||||||
|
]>
|
||||||
|
|
||||||
|
<refentry id="build-howto">
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>Compiling with &package_string;</refentrytitle>
|
||||||
|
<manvolnum>3</manvolnum>
|
||||||
|
</refmeta>
|
||||||
|
|
||||||
|
<refnamediv>
|
||||||
|
<refname>Compiling with &package_string;</refname><refpurpose>Notes on compiling</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
|
||||||
|
<refsect2>
|
||||||
|
<title>Using pkg-config</title>
|
||||||
|
|
||||||
|
<para> Like other GNOME libraries,
|
||||||
|
<application>&package_string;</application> uses
|
||||||
|
<application>pkg-config</application> to provide compiler options. The
|
||||||
|
package name is
|
||||||
|
"<literal>&package_ver_str;</literal>". So in
|
||||||
|
your <literal>configure.ac</literal> script, you might specify something
|
||||||
|
like: </para>
|
||||||
|
|
||||||
|
<informalexample><programlisting>
|
||||||
|
PKG_CHECK_MODULES(LIBFEEDBACK, [&package_string;-&package_api_version;])
|
||||||
|
AC_SUBST(LIBFEEDBACK_CFLAGS)
|
||||||
|
AC_SUBST(LIBFEEDBACK_LIBS)
|
||||||
|
</programlisting></informalexample>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Or if using Meson/Ninja use a <literal>dependency('&package_string;-&package_api_version;')
|
||||||
|
</literal> dependency.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The "<literal>&package_api_version;</literal>" in the package name is the "API version"
|
||||||
|
(indicating "the version of the <application>&package_string;</application> API
|
||||||
|
that first appeared in version &package_api_version;") and is essentially just part of
|
||||||
|
the package name.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
When using the Meson build system you can declare a dependency like
|
||||||
|
</para>
|
||||||
|
<informalexample><programlisting>
|
||||||
|
dependency(libfeedback-0.0')
|
||||||
|
</programlisting></informalexample>
|
||||||
|
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
<refsect2>
|
||||||
|
<title>Acknowledge the Instability</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Since the library is young and is still changing a lot, in order to use it you are required to acknowledge that your are using an unstable API.
|
||||||
|
To do so, <literal>LIBFEEDBACK_USE_UNSTABLE_API</literal> must be defined for compilation to succeed.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
From C code or any compatible language, you can prefix your inclusion of the &package_string; header like so:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<informalexample><programlisting>
|
||||||
|
#define LIBFEEDBACK_USE_UNSTABLE_API
|
||||||
|
#include <libfeedback.h>
|
||||||
|
</programlisting></informalexample>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Including individual headers rather than <literal>libfeedback.h</literal> is not
|
||||||
|
recommended.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You can also acknoledge this with the definition option of your C compiler, like <literal>-DFEEDBACK_USE_UNSTABLE_API</literal>.
|
||||||
|
This is required from Vala.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
To use &package_name; from Vala, you must define the acknowledgment in C via <literal>-X -DFEEDBACK_USE_UNSTABLE_API</literal>.
|
||||||
|
If your build system uses a two pass compilation and hence your Vala compiler outputs C (Meson, Automake, or using the <literal>--ccode</literal> Vala compiler option trigger that) then you must add <literal>-DLIBFEEDBACK_USE_UNSTABLE_API</literal> to your C compiler argments instead.
|
||||||
|
</para>
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
|
</refentry>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||||
|
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
|
||||||
|
[
|
||||||
|
<!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
|
||||||
|
<!ENTITY % gtkdocentities SYSTEM "xml/gtkdocentities.ent">
|
||||||
|
%gtkdocentities;
|
||||||
|
]>
|
||||||
|
<book id="index">
|
||||||
|
<bookinfo>
|
||||||
|
<title>&package_name; Reference Manual</title>
|
||||||
|
<releaseinfo>
|
||||||
|
<para>This document is the API reference for &package_name; &package_version;</para>
|
||||||
|
<para>
|
||||||
|
<ulink type="http" url="&package_url;">Libfeedback</ulink> is a library to trigger
|
||||||
|
feedback (such as haptic feedback) via events sent by your application.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
If you find any issues in this API reference, please report it using
|
||||||
|
<ulink type="http" url="&package_bugreport;">the bugtracker</ulink>.
|
||||||
|
</para>
|
||||||
|
</releaseinfo>
|
||||||
|
|
||||||
|
<copyright>
|
||||||
|
<year>2020</year>
|
||||||
|
<holder>Purism SPC</holder>
|
||||||
|
</copyright>
|
||||||
|
</bookinfo>
|
||||||
|
|
||||||
|
<chapter id="intro">
|
||||||
|
<title>Introduction</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The purpose of libfeedback is to trigger audible, haptic and visual feedback based on
|
||||||
|
events. Events are identified by strings.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<xi:include href="build-howto.xml"/>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<chapter id="core-api">
|
||||||
|
<title>Core API</title>
|
||||||
|
<xi:include href="xml/libfeedback.xml"/>
|
||||||
|
<xi:include href="xml/lfb-event.xml"/>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<chapter id="low-level-api">
|
||||||
|
<title>Low level api</title>
|
||||||
|
<xi:include href="xml/lfb-gdbus.xml"/>
|
||||||
|
<xi:include href="xml/lfb-names.xml"/>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<chapter id="dbus-interface">
|
||||||
|
<title>DBus Interface</title>
|
||||||
|
<xi:include href="../data/libfeedback-org.sigxcpu.Feedback.xml"/>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<chapter id="object-tree">
|
||||||
|
<title>Object Hierarchy</title>
|
||||||
|
<xi:include href="xml/tree_index.sgml"/>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<index id="api-index-full">
|
||||||
|
<title>API Index</title>
|
||||||
|
<xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
<index id="annotations-glossary">
|
||||||
|
<title>Annotations glossary</title>
|
||||||
|
<xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
</book>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
if get_option('gtk_doc')
|
||||||
|
|
||||||
|
subdir('xml')
|
||||||
|
|
||||||
|
private_headers = [
|
||||||
|
]
|
||||||
|
|
||||||
|
content_files = [
|
||||||
|
'build-howto.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix')
|
||||||
|
glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html')
|
||||||
|
docpath = join_paths(get_option('datadir'), 'gtk-doc', 'html')
|
||||||
|
|
||||||
|
gnome.gtkdoc('libfeedback',
|
||||||
|
main_xml: 'libfeedback-docs.xml',
|
||||||
|
src_dir: [
|
||||||
|
join_paths(meson.source_root(), 'libfeedback'),
|
||||||
|
join_paths(meson.build_root(), 'libfeedback'),
|
||||||
|
join_paths(meson.build_root(), 'data'),
|
||||||
|
],
|
||||||
|
dependencies: libfeedback_dep,
|
||||||
|
gobject_typesfile: 'libfeedback.types',
|
||||||
|
scan_args: [
|
||||||
|
'--rebuild-types',
|
||||||
|
'--ignore-headers=' + ' '.join(private_headers),
|
||||||
|
],
|
||||||
|
fixxref_args: [
|
||||||
|
'--html-dir=@0@'.format(docpath),
|
||||||
|
'--extra-dir=@0@'.format(join_paths(glib_docpath, 'glib')),
|
||||||
|
'--extra-dir=@0@'.format(join_paths(glib_docpath, 'gobject')),
|
||||||
|
'--extra-dir=@0@'.format(join_paths(glib_docpath, 'gio')),
|
||||||
|
'--extra-dir=@0@'.format(join_paths(glib_docpath, 'gi')),
|
||||||
|
],
|
||||||
|
install_dir: 'libfeedback',
|
||||||
|
content_files: content_files,
|
||||||
|
install: true)
|
||||||
|
|
||||||
|
endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<!ENTITY package "@PACKAGE@">
|
||||||
|
<!ENTITY package_bugreport "@PACKAGE_BUGREPORT@">
|
||||||
|
<!ENTITY package_name "@PACKAGE_NAME@">
|
||||||
|
<!ENTITY package_string "@PACKAGE_STRING@">
|
||||||
|
<!ENTITY package_tarname "@PACKAGE_TARNAME@">
|
||||||
|
<!ENTITY package_url "@PACKAGE_URL@">
|
||||||
|
<!ENTITY package_version "@PACKAGE_VERSION@">
|
||||||
|
<!ENTITY package_api_version "@PACKAGE_API_VERSION@">
|
||||||
|
<!ENTITY package_ver_str "@PACKAGE_STRING@-@PACKAGE_API_VERSION@">
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
ent_conf = configuration_data()
|
||||||
|
ent_conf.set('PACKAGE', 'libfeedback')
|
||||||
|
ent_conf.set('PACKAGE_BUGREPORT', 'https://source.puri.sm/Librem5/feedbackd/issues')
|
||||||
|
ent_conf.set('PACKAGE_NAME', 'libfeedback')
|
||||||
|
ent_conf.set('PACKAGE_STRING', 'libfeedback')
|
||||||
|
ent_conf.set('PACKAGE_TARNAME', 'libfeedback-' + meson.project_version())
|
||||||
|
ent_conf.set('PACKAGE_URL', 'https://source.puri.sm/Librem5/feedbackd')
|
||||||
|
ent_conf.set('PACKAGE_VERSION', meson.project_version())
|
||||||
|
ent_conf.set('PACKAGE_API_VERSION', apiversion)
|
||||||
|
configure_file(input: 'gtkdocentities.ent.in', output: 'gtkdocentities.ent', configuration: ent_conf)
|
||||||
|
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# export GI_TYPELIB_PATH=/usr/local/lib/x86_64-linux-gnu/girepository-1.0/
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Lfb', '0.0')
|
||||||
|
from gi.repository import Lfb
|
||||||
|
|
||||||
|
Lfb.init('org.sigxcpu.lfbexample')
|
||||||
|
event = Lfb.Event.new('phone-incoming-call')
|
||||||
|
event.trigger()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/usr/lib/*/girepository-1.0
|
||||||
@@ -0,0 +1,617 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libfeedback.h"
|
||||||
|
#include "lfb-priv.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:lfb-event
|
||||||
|
* @Short_description: An event triggering feedback to the user
|
||||||
|
* @Title: LfbEvent
|
||||||
|
*
|
||||||
|
* #LfbEvent represents an event that should trigger
|
||||||
|
* audio, haptic and/or visual feedback to the user by triggering
|
||||||
|
* feedback on a feedback daemon.
|
||||||
|
*
|
||||||
|
* One event can trigger several feedbacks at once (e.g. audio and
|
||||||
|
* haptic feedback). This is determined by the feedback theme in
|
||||||
|
* use (which is not under the appliction's control) and the active
|
||||||
|
* feedback profile (see #lfb_set_feedback_profile()).
|
||||||
|
*
|
||||||
|
* After initializing the library feedback can be triggered like
|
||||||
|
*
|
||||||
|
* |[
|
||||||
|
* g_autoptr (GError) err = NULL;
|
||||||
|
* LpfEvent *event = lfb_event_new ("message-new-instant");
|
||||||
|
* ret = lfb_event_trigger_feedback (event, &err);
|
||||||
|
* ]|
|
||||||
|
*
|
||||||
|
* When all feedback for this event has ended the #LfbEvent::feedback-ended
|
||||||
|
* signal is emitted. If you want to end the feedback ahead of time use
|
||||||
|
* #lfb_event_end_feedback ().
|
||||||
|
* Since these methods involve DBus calls there are asynchronous variants
|
||||||
|
* available, see e.g. #lfb_event_trigger_feedback_async().
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_EVENT,
|
||||||
|
PROP_TIMEOUT,
|
||||||
|
PROP_STATE,
|
||||||
|
PROP_END_REASON,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SIGNAL_FEEDBACK_ENDED,
|
||||||
|
N_SIGNALS,
|
||||||
|
};
|
||||||
|
static guint signals[N_SIGNALS];
|
||||||
|
|
||||||
|
typedef struct _LfbEvent {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
char *event;
|
||||||
|
gint timeout;
|
||||||
|
|
||||||
|
guint id;
|
||||||
|
LfbEventState state;
|
||||||
|
gint end_reason;
|
||||||
|
gulong handler_id;
|
||||||
|
} LfbEvent;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (LfbEvent, lfb_event, G_TYPE_OBJECT);
|
||||||
|
|
||||||
|
typedef struct _LpfAsyncData {
|
||||||
|
LfbEvent *event;
|
||||||
|
GTask *task;
|
||||||
|
} LpfAsyncData;
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_set_state (LfbEvent *self, LfbEventState state)
|
||||||
|
{
|
||||||
|
if (self->state == state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->state = state;
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_set_end_reason (LfbEvent *self, LfbEventEndReason reason)
|
||||||
|
{
|
||||||
|
if (self->end_reason == reason)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->end_reason = reason;
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_END_REASON]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GVariant *
|
||||||
|
build_hints (void)
|
||||||
|
{
|
||||||
|
GVariantBuilder hints_builder;
|
||||||
|
|
||||||
|
g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
|
||||||
|
return g_variant_new ("a{sv}", &hints_builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_trigger_feedback_finished (LfbGdbusFeedback *proxy,
|
||||||
|
GAsyncResult *res,
|
||||||
|
LpfAsyncData *data)
|
||||||
|
|
||||||
|
{
|
||||||
|
GTask *task = data->task;
|
||||||
|
LfbEvent *self = data->event;
|
||||||
|
GError *err = NULL;
|
||||||
|
gboolean success;
|
||||||
|
LfbEventState state;
|
||||||
|
|
||||||
|
g_return_if_fail (G_IS_TASK (task));
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
|
||||||
|
success = lfb_gdbus_feedback_call_trigger_feedback_finish (proxy,
|
||||||
|
&self->id,
|
||||||
|
res,
|
||||||
|
&err);
|
||||||
|
if (!success) {
|
||||||
|
g_task_return_error (task, err);
|
||||||
|
state = LFB_EVENT_STATE_ERRORED;
|
||||||
|
} else {
|
||||||
|
g_task_return_boolean (task, TRUE);
|
||||||
|
state = LFB_EVENT_STATE_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfb_event_set_state (self, state);
|
||||||
|
g_free (data);
|
||||||
|
g_object_unref (task);
|
||||||
|
g_object_unref (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_end_feedback_finished (LfbGdbusFeedback *proxy,
|
||||||
|
GAsyncResult *res,
|
||||||
|
LpfAsyncData *data)
|
||||||
|
|
||||||
|
{
|
||||||
|
GTask *task = data->task;
|
||||||
|
LfbEvent *self = data->event;
|
||||||
|
GError *err = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
g_return_if_fail (G_IS_TASK (task));
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
|
||||||
|
success = lfb_gdbus_feedback_call_end_feedback_finish (proxy,
|
||||||
|
res,
|
||||||
|
&err);
|
||||||
|
if (!success) {
|
||||||
|
g_task_return_error (task, err);
|
||||||
|
} else
|
||||||
|
g_task_return_boolean (task, TRUE);
|
||||||
|
|
||||||
|
g_free (data);
|
||||||
|
g_object_unref (task);
|
||||||
|
g_object_unref (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
LfbEvent *self = LFB_EVENT (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EVENT:
|
||||||
|
g_free (self->event);
|
||||||
|
self->event = g_value_dup_string (value);
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EVENT]);
|
||||||
|
break;
|
||||||
|
case PROP_TIMEOUT:
|
||||||
|
lfb_event_set_timeout (self, g_value_get_int (value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
LfbEvent *self = LFB_EVENT (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EVENT:
|
||||||
|
g_value_set_string (value, self->event);
|
||||||
|
break;
|
||||||
|
case PROP_TIMEOUT:
|
||||||
|
g_value_set_int (value, self->timeout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
LfbEvent *self = LFB_EVENT (object);
|
||||||
|
|
||||||
|
/* Signal handler is disconnected automatically due to due to g_signal_connect_object */
|
||||||
|
self->handler_id = 0;
|
||||||
|
|
||||||
|
g_clear_pointer (&self->event, g_free);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (lfb_event_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_class_init (LfbEventClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = lfb_event_set_property;
|
||||||
|
object_class->get_property = lfb_event_get_property;
|
||||||
|
|
||||||
|
object_class->finalize = lfb_event_finalize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LpfEvent:event:
|
||||||
|
*
|
||||||
|
* The type of event from the Event naming spec, e.g. 'message-new-instant'.
|
||||||
|
*/
|
||||||
|
props[PROP_EVENT] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"event",
|
||||||
|
"Event",
|
||||||
|
"The name of the event triggering the feedback",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LpfEvent:timeout:
|
||||||
|
*
|
||||||
|
* How long feedback should be provided in milliseconds. The special value
|
||||||
|
* %-1 uses the natural length of each feedback while %0 plays each feedback
|
||||||
|
* in a loop until ended explicitly.
|
||||||
|
*/
|
||||||
|
props[PROP_TIMEOUT] =
|
||||||
|
g_param_spec_int (
|
||||||
|
"timeout",
|
||||||
|
"Timeout",
|
||||||
|
"When the event should timeout",
|
||||||
|
-1, G_MAXINT, -1,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_STATE] =
|
||||||
|
g_param_spec_enum (
|
||||||
|
"state",
|
||||||
|
"State",
|
||||||
|
"The event's state",
|
||||||
|
LFB_TYPE_EVENT_STATE,
|
||||||
|
LFB_EVENT_END_REASON_NATURAL,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
props[PROP_END_REASON] =
|
||||||
|
g_param_spec_enum (
|
||||||
|
"end-reason",
|
||||||
|
"End reason",
|
||||||
|
"The reason why the feedbacks ended",
|
||||||
|
LFB_TYPE_EVENT_END_REASON,
|
||||||
|
LFB_EVENT_END_REASON_NATURAL,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LfbEvent::feedback-ended:
|
||||||
|
*
|
||||||
|
* Emitted when all feedbacks triggered by the event have ended.
|
||||||
|
*/
|
||||||
|
signals[SIGNAL_FEEDBACK_ENDED] = g_signal_new ("feedback-ended",
|
||||||
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lfb_event_init (LfbEvent *self)
|
||||||
|
{
|
||||||
|
self->timeout = -1;
|
||||||
|
self->state = LFB_EVENT_STATE_NONE;
|
||||||
|
self->end_reason = LFB_EVENT_END_REASON_NATURAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LfbEvent *
|
||||||
|
lfb_event_new (const char *event)
|
||||||
|
{
|
||||||
|
return g_object_new (LFB_TYPE_EVENT, "event", event, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_feedback_ended (LfbEvent *self,
|
||||||
|
guint event_id,
|
||||||
|
guint reason,
|
||||||
|
LfbGdbusFeedback *proxy)
|
||||||
|
{
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
|
||||||
|
if (event_id != self->id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lfb_event_set_end_reason (self, reason);
|
||||||
|
lfb_event_set_state (self, LFB_EVENT_STATE_ENDED);
|
||||||
|
g_signal_emit (self, signals[SIGNAL_FEEDBACK_ENDED], 0);
|
||||||
|
self->id = 0;
|
||||||
|
g_signal_handler_disconnect (proxy, self->handler_id);
|
||||||
|
self->handler_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_trigger_feedback:
|
||||||
|
* @self: The event to trigger feedback for.
|
||||||
|
* @error: The returned error information.
|
||||||
|
*
|
||||||
|
* Tells the feedback server to provide proper feedback for the give
|
||||||
|
* event to the user.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if successful. On error, this will return %FALSE and set
|
||||||
|
* @error.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_event_trigger_feedback (LfbEvent *self, GError **error)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), FALSE);
|
||||||
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||||
|
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before triggering events");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), FALSE);
|
||||||
|
|
||||||
|
if (self->handler_id == 0) {
|
||||||
|
self->handler_id = g_signal_connect_object (proxy,
|
||||||
|
"feedback-ended",
|
||||||
|
G_CALLBACK (on_feedback_ended),
|
||||||
|
self,
|
||||||
|
G_CONNECT_SWAPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
success = lfb_gdbus_feedback_call_trigger_feedback_sync (proxy,
|
||||||
|
lfb_get_app_id (),
|
||||||
|
self->event,
|
||||||
|
build_hints (),
|
||||||
|
self->timeout,
|
||||||
|
&self->id,
|
||||||
|
NULL,
|
||||||
|
error);
|
||||||
|
lfb_event_set_state (self, success ? LFB_EVENT_STATE_RUNNING : LFB_EVENT_STATE_ERRORED);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_trigger_feedback_async:
|
||||||
|
* @self: The event to trigger feedback for.
|
||||||
|
* @cancellable: (nullable): A #GCancellable or %NULL.
|
||||||
|
* @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
|
||||||
|
* @user_data: User data to pass to @callback.
|
||||||
|
*
|
||||||
|
* Tells the feedback server to provide proper feedback for the give
|
||||||
|
* event to the user. This is the sync version of
|
||||||
|
* #lfb_event_trigger_feedback.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_event_trigger_feedback_async (LfbEvent *self,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GAsyncReadyCallback callback,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
LpfAsyncData *data;
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before triggering events");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
|
||||||
|
data = g_new0 (LpfAsyncData, 1);
|
||||||
|
data->task = g_task_new (self, cancellable, callback, user_data);
|
||||||
|
data->event = g_object_ref (self);
|
||||||
|
lfb_gdbus_feedback_call_trigger_feedback (proxy,
|
||||||
|
lfb_get_app_id (),
|
||||||
|
self->event,
|
||||||
|
build_hints (),
|
||||||
|
self->timeout,
|
||||||
|
cancellable,
|
||||||
|
(GAsyncReadyCallback)on_trigger_feedback_finished,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_trigger_feedback_finish:
|
||||||
|
* @self: the event
|
||||||
|
* @res: Result object passed to the callback of
|
||||||
|
* #lfb_event_trigger_feedback_async
|
||||||
|
* @error: Return location for error
|
||||||
|
*
|
||||||
|
* Finish an async operation started by lfb_event_trigger_feedback_async. You
|
||||||
|
* must call this function in the callback to free memory and receive any
|
||||||
|
* errors which occurred.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if playing finished successfully
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_event_trigger_feedback_finish (LfbEvent *self,
|
||||||
|
GAsyncResult *res,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
|
||||||
|
|
||||||
|
return g_task_propagate_boolean (G_TASK (res), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_end_feedback:
|
||||||
|
* @self: The event to end feedback for.
|
||||||
|
* @error: The returned error information.
|
||||||
|
*
|
||||||
|
* Tells the feedback server to end all feedback for the given event as
|
||||||
|
* soon as possible.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if successful. On error, this will return %FALSE and set
|
||||||
|
* @error.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_event_end_feedback (LfbEvent *self, GError **error)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), FALSE);
|
||||||
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||||
|
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before ending events");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_val_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy), FALSE);
|
||||||
|
return lfb_gdbus_feedback_call_end_feedback_sync (proxy, self->id, NULL, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_end_feedback_finish:
|
||||||
|
* @self: the event
|
||||||
|
* @res: Result object passed to the callback of
|
||||||
|
* #lfb_event_end_feedback_async
|
||||||
|
* @error: Return location for error
|
||||||
|
*
|
||||||
|
* Finish an async operation started by lfb_event_end_feedback_async. You
|
||||||
|
* must call this function in the callback to free memory and receive any
|
||||||
|
* errors which occurred.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if playing finished successfully
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_event_end_feedback_finish (LfbEvent *self,
|
||||||
|
GAsyncResult *res,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
|
||||||
|
|
||||||
|
return g_task_propagate_boolean (G_TASK (res), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_end_feedback_async:
|
||||||
|
* @self: The event to end feedback for.
|
||||||
|
* @cancellable: (nullable): A #GCancellable or %NULL.
|
||||||
|
* @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
|
||||||
|
* @user_data: User data to pass to @callback.
|
||||||
|
*
|
||||||
|
* Tells the feedback server to end all feedback for the given event as
|
||||||
|
* soon as possible.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_event_end_feedback_async (LfbEvent *self,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GAsyncReadyCallback callback,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
LpfAsyncData *data;
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before ending events");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
|
||||||
|
data = g_new0 (LpfAsyncData, 1);
|
||||||
|
data->task = g_task_new (self, cancellable, callback, user_data);
|
||||||
|
data->event = g_object_ref (self);
|
||||||
|
lfb_gdbus_feedback_call_end_feedback (proxy,
|
||||||
|
self->id,
|
||||||
|
cancellable,
|
||||||
|
(GAsyncReadyCallback)on_end_feedback_finished,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_set_timeout:
|
||||||
|
* @self: The event
|
||||||
|
* @timeout: The timeout
|
||||||
|
*
|
||||||
|
* Tells the feedback server to end feedack after #timeout seconds.
|
||||||
|
* The value -1 indicates to not set a timeout and let feedbacks stop
|
||||||
|
* on their own while 0 indicates to loop all feedbacks endlessly.
|
||||||
|
* They must be stopped via #lfb_event_end_feedback () in this case.
|
||||||
|
*
|
||||||
|
* It is an error to change the timeout after the feedback has been triggered
|
||||||
|
* via lfb_event_trigger.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_event_set_timeout (LfbEvent *self, gint timeout)
|
||||||
|
{
|
||||||
|
g_return_if_fail (LFB_IS_EVENT (self));
|
||||||
|
|
||||||
|
if (self->timeout == timeout)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->timeout = timeout;
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIMEOUT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_get_event:
|
||||||
|
* @self: The event
|
||||||
|
*
|
||||||
|
* Get the event's name according to the event naming spec.
|
||||||
|
*
|
||||||
|
* Returns: The event name
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
lfb_event_get_event (LfbEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), NULL);
|
||||||
|
return self->event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_get_timeout:
|
||||||
|
* @self: The event
|
||||||
|
*
|
||||||
|
* Get the currently set timeout.
|
||||||
|
*
|
||||||
|
* Returns: The event timeout in msecs
|
||||||
|
*/
|
||||||
|
gint
|
||||||
|
lfb_event_get_timeout (LfbEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), -1);
|
||||||
|
return self->timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_get_state:
|
||||||
|
* @self: The event
|
||||||
|
*
|
||||||
|
* Get the current event state (e.g. if triggered feeedback is
|
||||||
|
* currently running.
|
||||||
|
*
|
||||||
|
* Returns: The state of the feedback triggered by event.
|
||||||
|
*/
|
||||||
|
LfbEventState
|
||||||
|
lfb_event_get_state (LfbEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), LFB_EVENT_STATE_NONE);
|
||||||
|
return self->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_event_get_end_reason:
|
||||||
|
* @self: The event
|
||||||
|
*
|
||||||
|
* Get the reason why the feadback ended.
|
||||||
|
*
|
||||||
|
* Returns: The reason why feedback ended.
|
||||||
|
*/
|
||||||
|
LfbEventEndReason
|
||||||
|
lfb_event_get_end_reason (LfbEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (LFB_IS_EVENT (self), LFB_EVENT_END_REASON_NATURAL);
|
||||||
|
return self->end_reason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
#if !defined (__LIBFEEDBACK_H_INSIDE__) && !defined (LIBFEEDBACK_COMPILATION)
|
||||||
|
#error "Only <libfeedback.h> can be included directly."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LfbEventState:
|
||||||
|
* @LFB_EVENT_STATE_ERRORED: An error occured triggering feedbacks
|
||||||
|
* @LFB_EVENT_STATE_NONE: No state information yet
|
||||||
|
* @LFB_EVENT_STATE_RUNNING: The feedbacks for this event are currently running
|
||||||
|
* @LFB_EVENT_STATE_ENDED: All feedbacks for this event ended
|
||||||
|
*
|
||||||
|
* Enum values to indicate the current state of the feedbacks
|
||||||
|
* triggered by an event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum _LfbEventState {
|
||||||
|
LFB_EVENT_STATE_ERRORED = -1,
|
||||||
|
LFB_EVENT_STATE_NONE = 0,
|
||||||
|
LFB_EVENT_STATE_RUNNING = 1,
|
||||||
|
LFB_EVENT_STATE_ENDED = 2,
|
||||||
|
} LfbEventState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LfbEventEndReason:
|
||||||
|
* @LFB_EVENT_END_REASON_NOT_FOUND: There was no feedback in the current theme for this event
|
||||||
|
* so no feedback was provided to the user.
|
||||||
|
* @LFB_EVENT_END_REASON_NATURAL: All feedbacks finished playing their natural length
|
||||||
|
* @LFB_EVENT_END_REASON_EXPIRED: Feedbacks ran until the set timeout expired
|
||||||
|
* @LFB_EVENT_END_REASON_EXPLICIT: The feedbacks were ended explicitly
|
||||||
|
*
|
||||||
|
* Enum values used to indicate why the feedbacks for an event ended.
|
||||||
|
**/
|
||||||
|
typedef enum _LfbEventEndReason {
|
||||||
|
|
||||||
|
LFB_EVENT_END_REASON_NOT_FOUND = -1,
|
||||||
|
LFB_EVENT_END_REASON_NATURAL = 0,
|
||||||
|
LFB_EVENT_END_REASON_EXPIRED = 1,
|
||||||
|
LFB_EVENT_END_REASON_EXPLICIT = 2,
|
||||||
|
} LfbEventEndReason;
|
||||||
|
|
||||||
|
#define LFB_TYPE_EVENT (lfb_event_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (LfbEvent, lfb_event, LFB, EVENT, GObject);
|
||||||
|
|
||||||
|
LfbEvent* lfb_event_new (const char *event);
|
||||||
|
gboolean lfb_event_trigger_feedback (LfbEvent *self, GError **error);
|
||||||
|
void lfb_event_trigger_feedback_async (LfbEvent *self,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GAsyncReadyCallback callback,
|
||||||
|
gpointer user_data);
|
||||||
|
gboolean lfb_event_trigger_feedback_finish (LfbEvent *self,
|
||||||
|
GAsyncResult *res,
|
||||||
|
GError **error);
|
||||||
|
gboolean lfb_event_end_feedback (LfbEvent *self, GError **error);
|
||||||
|
void lfb_event_end_feedback_async (LfbEvent *self,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GAsyncReadyCallback callback,
|
||||||
|
gpointer user_data);
|
||||||
|
gboolean lfb_event_end_feedback_finish (LfbEvent *self,
|
||||||
|
GAsyncResult *res,
|
||||||
|
GError **error);
|
||||||
|
void lfb_event_set_timeout (LfbEvent *self, gint timeout);
|
||||||
|
gint lfb_event_get_timeout (LfbEvent *self);
|
||||||
|
const char *lfb_event_get_event (LfbEvent *self);
|
||||||
|
LfbEventState lfb_event_get_state (LfbEvent *self);
|
||||||
|
LfbEventEndReason lfb_event_get_end_reason (LfbEvent *self);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define FB_DBUS_NAME "org.sigxcpu.Feedback"
|
||||||
|
#define FB_DBUS_PATH "/org/sigxcpu/Feedback"
|
||||||
|
|
||||||
|
#define FB_DBUS_TYPE G_BUS_TYPE_SESSION
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include "lfb-gdbus.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
LfbGdbusFeedback *_lfb_get_proxy (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
#include "libfeedback.h"
|
||||||
|
#include "lfb-gdbus.h"
|
||||||
|
#include "lfb-priv.h"
|
||||||
|
|
||||||
|
#include "lfb-names.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:libfeedback
|
||||||
|
* @Short_description: Library initialization and helpers
|
||||||
|
* @Title: libfeedback
|
||||||
|
*
|
||||||
|
* To use the library call #lfb_init() with the id of your application
|
||||||
|
* (usually the desktop file name without the .desktop extension).
|
||||||
|
* When done call #lfb_uninit() to free any resources.
|
||||||
|
*
|
||||||
|
* |[
|
||||||
|
* #define LIBFEEDBACK_USE_UNSTABLE_API
|
||||||
|
* #include <libfeedback.h>
|
||||||
|
*
|
||||||
|
* int main(void)
|
||||||
|
* {
|
||||||
|
* g_autoptr (GError) *err = NULL;
|
||||||
|
* if (lfb_init ("com.example.appid", &err)) {
|
||||||
|
* g_error ("%s", err->message);
|
||||||
|
* }
|
||||||
|
* // run your application, emit events
|
||||||
|
* // ...
|
||||||
|
* // when done:
|
||||||
|
* lfb_uninit ();
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* ]|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static LfbGdbusFeedback *_proxy;
|
||||||
|
static char *_app_id;
|
||||||
|
static gboolean _initted;
|
||||||
|
|
||||||
|
LfbGdbusFeedback *
|
||||||
|
_lfb_get_proxy (void)
|
||||||
|
{
|
||||||
|
/* Caller needs to check since the proxy might be gone when
|
||||||
|
the last event gets finalized */
|
||||||
|
return _proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_init:
|
||||||
|
* @app_id: The application id
|
||||||
|
* @error: Error information
|
||||||
|
*
|
||||||
|
* Initialize libfeedback. This must be called before any other functions.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if successful, or %FALSE on error.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_init (const gchar *app_id, GError **error)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (app_id != NULL, FALSE);
|
||||||
|
g_return_val_if_fail (*app_id != '\0', FALSE);
|
||||||
|
|
||||||
|
if (_initted)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
lfb_set_app_id (app_id);
|
||||||
|
_proxy = lfb_gdbus_feedback_proxy_new_for_bus_sync(
|
||||||
|
FB_DBUS_TYPE, 0, FB_DBUS_NAME, FB_DBUS_PATH, NULL, error);
|
||||||
|
if (!_proxy)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (_proxy), (gpointer *) &_proxy);
|
||||||
|
|
||||||
|
_initted = TRUE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_uninit:
|
||||||
|
*
|
||||||
|
* Uninitialize the library when no longer used. Usually called
|
||||||
|
* on program shutdown.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_uninit (void)
|
||||||
|
{
|
||||||
|
g_clear_pointer (&_app_id, g_free);
|
||||||
|
g_clear_object (&_proxy);
|
||||||
|
_initted = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_set_app_id:
|
||||||
|
* @app_id: The application id
|
||||||
|
*
|
||||||
|
* Sets the application id.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_set_app_id (const char *app_id)
|
||||||
|
{
|
||||||
|
g_free (_app_id);
|
||||||
|
_app_id = g_strdup (app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_get_app_id:
|
||||||
|
*
|
||||||
|
* Returns: the application id.
|
||||||
|
*/
|
||||||
|
const gchar *
|
||||||
|
lfb_get_app_id (void)
|
||||||
|
{
|
||||||
|
return _app_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_is_initted:
|
||||||
|
*
|
||||||
|
* Gets whether or not libfeedback is initialized.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if libfeedback is initialized, or %FALSE otherwise.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
lfb_is_initted (void)
|
||||||
|
{
|
||||||
|
return _initted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_get_feedback_profile:
|
||||||
|
*
|
||||||
|
* Gets the currently set feedback profile.
|
||||||
|
*
|
||||||
|
* Returns: The current profile or %NULL on error.
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
lfb_get_feedback_profile (void)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
g_autofree gchar *profile = NULL;
|
||||||
|
g_autoptr(GVariant) var = NULL;
|
||||||
|
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before getting the profile");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_val_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy), NULL);
|
||||||
|
|
||||||
|
return lfb_gdbus_feedback_get_profile (LFB_GDBUS_FEEDBACK (proxy));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_set_feedback_profile:
|
||||||
|
* @profile: The profile to set
|
||||||
|
*
|
||||||
|
* Sets the active feedback profile to #profile. It is up to the feedback
|
||||||
|
* daemon to ignore this request. The new profile might not become active
|
||||||
|
* immediately. You can listen to changes #LfbGdbusFeedback's ::profile
|
||||||
|
* property to get notified when it takes effect.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
lfb_set_feedback_profile (const gchar *profile)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
|
||||||
|
if (!lfb_is_initted ()) {
|
||||||
|
g_warning ("you must call lfb_init() before setting the profile");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy = _lfb_get_proxy ();
|
||||||
|
g_return_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy));
|
||||||
|
|
||||||
|
lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (proxy), profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lfb_get_proxy:
|
||||||
|
*
|
||||||
|
* This can be used to access the lower level API e.g. to listen to
|
||||||
|
* property changes. The object is not owned by the caller. Don't
|
||||||
|
* unref it after use.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): The DBus proxy.
|
||||||
|
*/
|
||||||
|
LfbGdbusFeedback *
|
||||||
|
lfb_get_proxy (void)
|
||||||
|
{
|
||||||
|
LfbGdbusFeedback *proxy = _lfb_get_proxy ();
|
||||||
|
|
||||||
|
g_return_val_if_fail (LFB_GDBUS_IS_FEEDBACK (proxy), NULL);
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define __LIBFEEDBACK_H_INSIDE__
|
||||||
|
|
||||||
|
#ifndef LIBFEEDBACK_USE_UNSTABLE_API
|
||||||
|
#error LIBFEEDBACK is unstable API. You must define LIBFEEDBACK_USE_UNSTABLE_API before including libfeedback.h
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lfb-enums.h"
|
||||||
|
#include "lfb-event.h"
|
||||||
|
#include "lfb-gdbus.h"
|
||||||
|
|
||||||
|
gboolean lfb_init (const gchar *app_id, GError **error);
|
||||||
|
void lfb_uninit (void);
|
||||||
|
void lfb_set_app_id (const char *app_id);
|
||||||
|
const char *lfb_get_app_id (void);
|
||||||
|
gboolean lfb_is_initted (void);
|
||||||
|
void lfb_set_feedback_profile (const char *profile);
|
||||||
|
const char *lfb_get_feedback_profile (void);
|
||||||
|
LfbGdbusFeedback *lfb_get_proxy (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Shared library and language bindings
|
||||||
|
|
||||||
|
libfeedback_libdir = get_option('libdir')
|
||||||
|
libfeedback_header_dir = get_option('includedir') / libname
|
||||||
|
|
||||||
|
lfb_enum_headers = files([
|
||||||
|
'lfb-event.h',
|
||||||
|
])
|
||||||
|
lfb_enum_sources = gnome.mkenums_simple('lfb-enums',
|
||||||
|
sources : lfb_enum_headers,
|
||||||
|
install_header : true,
|
||||||
|
install_dir: libfeedback_header_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
libfeedback_headers = [
|
||||||
|
'libfeedback.h',
|
||||||
|
'lfb-event.h',
|
||||||
|
]
|
||||||
|
|
||||||
|
libfeedback_sources = [
|
||||||
|
generated_dbus_sources,
|
||||||
|
lfb_enum_sources,
|
||||||
|
libfeedback_headers,
|
||||||
|
'libfeedback.c',
|
||||||
|
'lfb-event.c',
|
||||||
|
]
|
||||||
|
|
||||||
|
libfeedback_deps = [
|
||||||
|
gio,
|
||||||
|
gio_unix,
|
||||||
|
glib,
|
||||||
|
]
|
||||||
|
|
||||||
|
libfeedback_c_args = [
|
||||||
|
'-DG_LOG_DOMAIN="libfeedback"',
|
||||||
|
'-DFEEDBACK_COMPILATION',
|
||||||
|
'-DLIBFEEDBACK_USE_UNSTABLE_API',
|
||||||
|
]
|
||||||
|
|
||||||
|
libtype = 'shared_library'
|
||||||
|
libfeedback = build_target(
|
||||||
|
'feedback-' + apiversion,
|
||||||
|
libfeedback_sources,
|
||||||
|
soversion : 0,
|
||||||
|
c_args : libfeedback_c_args,
|
||||||
|
dependencies : libfeedback_deps,
|
||||||
|
install : true,
|
||||||
|
install_dir : libfeedback_libdir,
|
||||||
|
target_type : libtype,
|
||||||
|
)
|
||||||
|
|
||||||
|
libfeedback_inc = include_directories('.')
|
||||||
|
libfeedback_dep = declare_dependency(
|
||||||
|
sources : [generated_dbus_sources, lfb_enum_sources],
|
||||||
|
link_with : libfeedback,
|
||||||
|
dependencies: libfeedback_deps,
|
||||||
|
include_directories : libfeedback_inc,
|
||||||
|
)
|
||||||
|
install_headers(libfeedback_headers, subdir : libname)
|
||||||
|
|
||||||
|
pkgconfig = import('pkgconfig')
|
||||||
|
|
||||||
|
pkgconfig.generate(
|
||||||
|
libraries : [libfeedback],
|
||||||
|
subdirs : libname,
|
||||||
|
version : meson.project_version(),
|
||||||
|
name : 'Feedback',
|
||||||
|
filebase : 'libfeedback-@0@'.format(apiversion),
|
||||||
|
description : 'GNOME feedback library',
|
||||||
|
requires : 'gio-2.0',
|
||||||
|
install_dir : join_paths(libfeedback_libdir, 'pkgconfig'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if introspection
|
||||||
|
libhandy_gir_extra_args = [
|
||||||
|
'-I' + dbus_header_dir,
|
||||||
|
'--c-include=libfeedback.h',
|
||||||
|
'--quiet',
|
||||||
|
'-DLIBFEEDBACK_COMPILATION',
|
||||||
|
'-DLIBFEEDBACK_USE_UNSTABLE_API',
|
||||||
|
'--warn-error',
|
||||||
|
'--warn-all',
|
||||||
|
]
|
||||||
|
|
||||||
|
libfeedback_gir = gnome.generate_gir(libfeedback,
|
||||||
|
sources : libfeedback_sources,
|
||||||
|
nsversion : apiversion,
|
||||||
|
namespace : 'Lfb',
|
||||||
|
export_packages : 'libfeedback-@0@'.format(apiversion),
|
||||||
|
symbol_prefix : 'lfb',
|
||||||
|
identifier_prefix : 'Lfb',
|
||||||
|
link_with : libfeedback,
|
||||||
|
includes : ['Gio-2.0'],
|
||||||
|
install : true,
|
||||||
|
install_dir_gir : girdir,
|
||||||
|
install_dir_typelib : typelibdir,
|
||||||
|
extra_args : libhandy_gir_extra_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
if get_option('vapi')
|
||||||
|
libfeedback_vapi = gnome.generate_vapi('libfeedback-' + apiversion,
|
||||||
|
sources : libfeedback_gir[0],
|
||||||
|
packages : ['gio-2.0'],
|
||||||
|
install : true,
|
||||||
|
install_dir : vapidir,
|
||||||
|
metadata_dirs : [meson.current_source_dir()],
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
project('feedbackd', 'c',
|
||||||
|
version : '0.0.0',
|
||||||
|
license : 'LGPLv3+',
|
||||||
|
meson_version : '>= 0.49.0',
|
||||||
|
default_options : [
|
||||||
|
'warning_level=1',
|
||||||
|
'buildtype=debugoptimized',
|
||||||
|
'c_std=gnu11',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
gio = dependency('gio-2.0', version: '>=2.50.0')
|
||||||
|
gio_unix = dependency('gio-unix-2.0', version: '>=2.50.0')
|
||||||
|
glib = dependency('glib-2.0', version: '>=2.50.0')
|
||||||
|
gobject = dependency('gobject-2.0', version: '>=2.50.0')
|
||||||
|
gsound = dependency('gsound')
|
||||||
|
gudev = dependency('gudev-1.0', version: '>=232')
|
||||||
|
json_glib = dependency('json-glib-1.0')
|
||||||
|
|
||||||
|
prefix = get_option('prefix')
|
||||||
|
|
||||||
|
datadir = join_paths(prefix, get_option('datadir'))
|
||||||
|
libdir = join_paths(prefix, get_option('libdir'))
|
||||||
|
girdir = join_paths(prefix, join_paths(datadir, 'gir-1.0'))
|
||||||
|
ifacedir = join_paths(prefix, join_paths(datadir, 'dbus-1', 'interfaces'))
|
||||||
|
servicedir = join_paths(prefix, join_paths(datadir, 'dbus-1', 'services'))
|
||||||
|
libexecdir = join_paths(prefix, get_option('libexecdir'))
|
||||||
|
typelibdir = join_paths(prefix, join_paths(libdir, 'girepository-1.0'))
|
||||||
|
vapidir = join_paths(prefix, join_paths(datadir, 'vala', 'vapi'))
|
||||||
|
feedbackd_theme_dir = join_paths(datadir, 'feedbackd', 'themes')
|
||||||
|
|
||||||
|
run_data = configuration_data()
|
||||||
|
run_data.set('ABS_BUILDDIR', meson.current_build_dir())
|
||||||
|
run_data.set('ABS_SRCDIR', meson.current_source_dir())
|
||||||
|
configure_file(
|
||||||
|
input: 'run.in',
|
||||||
|
output: 'run',
|
||||||
|
configuration: run_data)
|
||||||
|
|
||||||
|
gnome = import('gnome')
|
||||||
|
|
||||||
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
|
test_c_args = [
|
||||||
|
'-Wcast-align',
|
||||||
|
'-Wdate-time',
|
||||||
|
['-Werror=format-security', '-Werror=format=2'],
|
||||||
|
'-Wendif-labels',
|
||||||
|
'-Werror=incompatible-pointer-types',
|
||||||
|
'-Werror=missing-declarations',
|
||||||
|
'-Werror=overflow',
|
||||||
|
'-Werror=return-type',
|
||||||
|
'-Werror=shift-count-overflow',
|
||||||
|
'-Werror=shift-overflow=2',
|
||||||
|
'-Werror=implicit-fallthrough=3',
|
||||||
|
'-Wformat-nonliteral',
|
||||||
|
'-Wformat-security',
|
||||||
|
'-Winit-self',
|
||||||
|
'-Wmaybe-uninitialized',
|
||||||
|
'-Wmissing-field-initializers',
|
||||||
|
'-Wmissing-include-dirs',
|
||||||
|
'-Wmissing-noreturn',
|
||||||
|
'-Wnested-externs',
|
||||||
|
'-Wno-missing-field-initializers',
|
||||||
|
'-Wno-sign-compare',
|
||||||
|
'-Wno-strict-aliasing',
|
||||||
|
'-Wno-unused-parameter',
|
||||||
|
'-Wold-style-definition',
|
||||||
|
'-Wpointer-arith',
|
||||||
|
'-Wredundant-decls',
|
||||||
|
'-Wstrict-prototypes',
|
||||||
|
'-Wtype-limits',
|
||||||
|
'-Wundef',
|
||||||
|
'-Wunused-function',
|
||||||
|
]
|
||||||
|
|
||||||
|
if get_option('buildtype') != 'plain'
|
||||||
|
test_c_args += '-fstack-protector-strong'
|
||||||
|
endif
|
||||||
|
|
||||||
|
global_c_args = [
|
||||||
|
'-DFEEDBACKD_THEME_DIR="@0@"'.format(feedbackd_theme_dir),
|
||||||
|
'-DLIBFEEDBACK_COMPILATION',
|
||||||
|
]
|
||||||
|
|
||||||
|
add_project_arguments(
|
||||||
|
global_c_args + cc.get_supported_arguments(test_c_args),
|
||||||
|
language: 'c'
|
||||||
|
)
|
||||||
|
|
||||||
|
introspection_feature = get_option('introspection')
|
||||||
|
introspection = introspection_feature.enabled() or introspection_feature.auto()
|
||||||
|
|
||||||
|
apiversion = '0.0'
|
||||||
|
libname = 'libfeedback-' + apiversion
|
||||||
|
|
||||||
|
json_glib_validate = find_program('json-glib-validate', required: false)
|
||||||
|
|
||||||
|
subdir('data')
|
||||||
|
subdir('libfeedback')
|
||||||
|
subdir('src')
|
||||||
|
subdir('cli')
|
||||||
|
subdir('tests')
|
||||||
|
subdir('doc')
|
||||||
|
|
||||||
|
summary = [
|
||||||
|
'',
|
||||||
|
'---------------',
|
||||||
|
'feedbackd @0@'.format(meson.project_version()),
|
||||||
|
'',
|
||||||
|
' Daemon: @0@'.format(get_option('daemon')),
|
||||||
|
' Documentation: @0@'.format(get_option('gtk_doc')),
|
||||||
|
' Introspection: @0@'.format(introspection),
|
||||||
|
' Vapi: @0@'.format(get_option('vapi')),
|
||||||
|
' Tests: @0@'.format(get_option('tests')),
|
||||||
|
'---------------',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
message('\n'.join(summary))
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
option('introspection', type: 'feature', value: 'auto')
|
||||||
|
option('vapi', type: 'boolean', value: true)
|
||||||
|
|
||||||
|
option('daemon',
|
||||||
|
type: 'boolean', value: true,
|
||||||
|
description: 'Whether to build the daeon')
|
||||||
|
option('gtk_doc',
|
||||||
|
type: 'boolean', value: false,
|
||||||
|
description: 'Whether to generate the API reference for Handy')
|
||||||
|
option('tests',
|
||||||
|
type: 'boolean', value: true,
|
||||||
|
description: 'Whether to compile unit tests')
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ABS_BUILDDIR='@ABS_BUILDDIR@'
|
||||||
|
ABS_SRCDIR='@ABS_SRCDIR@'
|
||||||
|
FEEDBACK_THEME=${FEEDBACK_THEME:-"${ABS_SRCDIR}/data/default.json"}
|
||||||
|
|
||||||
|
export FEEDBACK_THEME
|
||||||
|
export GSETTINGS_SCHEMA_DIR="${ABS_BUILDDIR}/data"
|
||||||
|
|
||||||
|
set -x
|
||||||
|
exec "${ABS_BUILDDIR}/src/feedbackd" "$@"
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*
|
||||||
|
* See https://www.kernel.org/doc/html/latest/input/ff.html
|
||||||
|
* and fftest.c from the joystick package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-dev-sound"
|
||||||
|
|
||||||
|
#include "fbd-dev-sound.h"
|
||||||
|
#include "fbd-feedback-sound.h"
|
||||||
|
|
||||||
|
#include <gsound.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-dev-sound
|
||||||
|
* @short_description: Sound interface
|
||||||
|
* @Title: FbdDevSound
|
||||||
|
*
|
||||||
|
* The #FbdDevSound is used to play sounds via the systems audio
|
||||||
|
* system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _FbdAsyncData {
|
||||||
|
FbdDevSoundPlayedCallback callback;
|
||||||
|
FbdFeedbackSound *feedback;
|
||||||
|
} FbdAsyncData;
|
||||||
|
|
||||||
|
typedef struct _FbdDevSound {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
GSoundContext *ctx;
|
||||||
|
} FbdDevSound;
|
||||||
|
|
||||||
|
static void initable_iface_init (GInitableIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (FbdDevSound, fbd_dev_sound, G_TYPE_OBJECT,
|
||||||
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_sound_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdDevSound *self = FBD_DEV_SOUND (object);
|
||||||
|
|
||||||
|
g_clear_object (&self->ctx);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_dev_sound_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
initable_init (GInitable *initable,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
FbdDevSound *self = FBD_DEV_SOUND (initable);
|
||||||
|
|
||||||
|
self->ctx = gsound_context_new(NULL, error);
|
||||||
|
if (!self->ctx)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
initable_iface_init (GInitableIface *iface)
|
||||||
|
{
|
||||||
|
iface->init = initable_init;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_sound_class_init (FbdDevSoundClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->dispose = fbd_dev_sound_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_sound_init (FbdDevSound *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdDevSound *
|
||||||
|
fbd_dev_sound_new (GError **error)
|
||||||
|
{
|
||||||
|
return FBD_DEV_SOUND (g_initable_new (FBD_TYPE_DEV_SOUND,
|
||||||
|
NULL,
|
||||||
|
error,
|
||||||
|
NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_sound_play_finished_callback (GSoundContext *ctx,
|
||||||
|
GAsyncResult *res,
|
||||||
|
FbdAsyncData *data)
|
||||||
|
{
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (!gsound_context_play_full_finish (ctx, res, &err)) {
|
||||||
|
g_warning ("Failed to play sound: %s", err->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*data->callback)(data->feedback);
|
||||||
|
g_object_unref (data->feedback);
|
||||||
|
g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_dev_sound_play (FbdDevSound *self, FbdFeedbackSound *feedback, FbdDevSoundPlayedCallback callback)
|
||||||
|
{
|
||||||
|
FbdAsyncData *data;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_SOUND (self), FALSE);
|
||||||
|
g_return_val_if_fail (GSOUND_IS_CONTEXT (self->ctx), FALSE);
|
||||||
|
|
||||||
|
data = g_new0 (FbdAsyncData, 1);
|
||||||
|
data->callback = callback;
|
||||||
|
data->feedback = g_object_ref (feedback);
|
||||||
|
|
||||||
|
gsound_context_play_full (self->ctx, NULL,
|
||||||
|
(GAsyncReadyCallback)on_sound_play_finished_callback,
|
||||||
|
data,
|
||||||
|
GSOUND_ATTR_EVENT_ID, fbd_feedback_sound_get_effect (feedback),
|
||||||
|
GSOUND_ATTR_EVENT_DESCRIPTION, "Feedbackd sound feedback",
|
||||||
|
NULL);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
#include "fbd-feedback-sound.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_DEV_SOUND (fbd_dev_sound_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdDevSound, fbd_dev_sound, FBD, DEV_SOUND, GObject);
|
||||||
|
|
||||||
|
typedef void (*FbdDevSoundPlayedCallback)(FbdFeedbackSound *feedback);
|
||||||
|
|
||||||
|
FbdDevSound *fbd_dev_sound_new (GError **error);
|
||||||
|
gboolean fbd_dev_sound_play (FbdDevSound *self,
|
||||||
|
FbdFeedbackSound *feedback,
|
||||||
|
FbdDevSoundPlayedCallback callback);
|
||||||
|
gboolean fbd_dev_sound_stop (FbdDevSound *self, FbdFeedbackSound *feedback);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*
|
||||||
|
* See https://www.kernel.org/doc/html/latest/input/ff.html
|
||||||
|
* and fftest.c from the joystick package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-dev-vibra"
|
||||||
|
|
||||||
|
#include "fbd-dev-vibra.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-dev-vibra
|
||||||
|
* @short_description: Haptic motor device interface
|
||||||
|
* @Title: FbdDevVibra
|
||||||
|
*
|
||||||
|
* The #FbdDevVibra is used to interface with haptic motor via the force
|
||||||
|
* feedback interface. It currently only supports one id at a time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_DEVICE,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FBD_DEV_VIBRA_FEATURE_RUMBLE,
|
||||||
|
FBD_DEV_VIBRA_FEATURE_PERIODIC,
|
||||||
|
FBD_DEV_VIBRA_FEATURE_GAIN,
|
||||||
|
} FbdDevVibraFeatureFlags;
|
||||||
|
|
||||||
|
typedef struct _FbdDevVibra {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
GUdevDevice *device;
|
||||||
|
gint fd;
|
||||||
|
gint id; /* currently used id */
|
||||||
|
|
||||||
|
FbdDevVibraFeatureFlags features;
|
||||||
|
} FbdDevVibra;
|
||||||
|
|
||||||
|
static void initable_iface_init (GInitableIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (FbdDevVibra, fbd_dev_vibra, G_TYPE_OBJECT,
|
||||||
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_DEVICE:
|
||||||
|
g_clear_object (&self->device);
|
||||||
|
self->device = g_value_dup_object (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
switch (property_id) {
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BITS_PER_LONG (8 * sizeof (long))
|
||||||
|
|
||||||
|
#define HAS_FEATURE(fbit, a) \
|
||||||
|
(a[(fbit/BITS_PER_LONG)] >> ((fbit % BITS_PER_LONG)) & 1)
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
initable_init (GInitable *initable,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
FbdDevVibra *self = FBD_DEV_VIBRA (initable);
|
||||||
|
const char *filename = g_udev_device_get_device_file (self->device);
|
||||||
|
gulong features[1 + FF_MAX/BITS_PER_LONG];
|
||||||
|
struct input_event gain = { 0 };
|
||||||
|
|
||||||
|
self->fd = open (filename, O_RDWR | O_NONBLOCK, O_RDWR);
|
||||||
|
if (self->fd < 0) {
|
||||||
|
g_set_error (error,
|
||||||
|
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||||
|
"Unable to open '%s': %s",
|
||||||
|
filename, g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl (self->fd, EVIOCGBIT(EV_FF, sizeof (features)), features) == -1) {
|
||||||
|
g_set_error (error,
|
||||||
|
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||||
|
"Unable to probe features of '%s': %s",
|
||||||
|
filename, g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HAS_FEATURE(FF_RUMBLE, features))
|
||||||
|
self->features |= FBD_DEV_VIBRA_FEATURE_RUMBLE;
|
||||||
|
else {
|
||||||
|
g_set_error (error,
|
||||||
|
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||||
|
"No rumble capable vibra device “%s”: %s",
|
||||||
|
filename, g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HAS_FEATURE(FF_PERIODIC, features))
|
||||||
|
self->features |= FBD_DEV_VIBRA_FEATURE_PERIODIC;
|
||||||
|
else {
|
||||||
|
g_set_error (error,
|
||||||
|
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||||
|
"No rumble capable vibra device “%s”: %s",
|
||||||
|
filename, g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set gain to 75% if supported */
|
||||||
|
if (HAS_FEATURE(FF_GAIN, features)) {
|
||||||
|
self->features |= FBD_DEV_VIBRA_FEATURE_GAIN;
|
||||||
|
memset(&gain, 0, sizeof(gain));
|
||||||
|
gain.type = EV_FF;
|
||||||
|
gain.code = FF_GAIN;
|
||||||
|
gain.value = 0xC000; /* [0, 0xFFFF]) */
|
||||||
|
|
||||||
|
g_debug("Setting master gain to 75%%");
|
||||||
|
if (write(self->fd, &gain, sizeof(gain)) != sizeof(gain)) {
|
||||||
|
g_set_error (error,
|
||||||
|
G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||||
|
"Unable to set gain of '%s': %s",
|
||||||
|
filename, g_strerror (errno));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g_debug ("Gain unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug ("Vibra device at '%s' usable", filename);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
initable_iface_init (GInitableIface *iface)
|
||||||
|
{
|
||||||
|
iface->init = initable_init;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||||
|
|
||||||
|
g_clear_object (&self->device);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_dev_vibra_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
FbdDevVibra *self = FBD_DEV_VIBRA (object);
|
||||||
|
|
||||||
|
if (self->fd >= 0) {
|
||||||
|
close (self->fd);
|
||||||
|
self->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_dev_vibra_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_class_init (FbdDevVibraClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_dev_vibra_set_property;
|
||||||
|
object_class->get_property = fbd_dev_vibra_get_property;
|
||||||
|
|
||||||
|
object_class->dispose = fbd_dev_vibra_dispose;
|
||||||
|
object_class->finalize = fbd_dev_vibra_finalize;
|
||||||
|
|
||||||
|
props[PROP_DEVICE] =
|
||||||
|
g_param_spec_object (
|
||||||
|
"device",
|
||||||
|
"Device",
|
||||||
|
"The udev device",
|
||||||
|
G_UDEV_TYPE_DEVICE,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_dev_vibra_init (FbdDevVibra *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdDevVibra *
|
||||||
|
fbd_dev_vibra_new (GUdevDevice *device, GError **error)
|
||||||
|
{
|
||||||
|
return FBD_DEV_VIBRA (g_initable_new (FBD_TYPE_DEV_VIBRA,
|
||||||
|
NULL,
|
||||||
|
error,
|
||||||
|
"device", device,
|
||||||
|
NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_dev_vibra_rumble (FbdDevVibra *self, guint duration, gboolean upload)
|
||||||
|
{
|
||||||
|
struct input_event event = { 0 };
|
||||||
|
struct ff_effect effect = { 0 };
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||||
|
|
||||||
|
memset(&effect, 0, sizeof(effect));
|
||||||
|
effect.type = FF_RUMBLE;
|
||||||
|
effect.id = -1;
|
||||||
|
effect.u.rumble.strong_magnitude = 0x8000;
|
||||||
|
effect.u.rumble.weak_magnitude = 0;
|
||||||
|
effect.replay.length = duration;
|
||||||
|
effect.replay.delay = 0;
|
||||||
|
|
||||||
|
if (upload) {
|
||||||
|
g_debug("Uploading rumbling vibra effect (%d)", self->fd);
|
||||||
|
if (ioctl(self->fd, EVIOCSFF, &effect) == -1) {
|
||||||
|
g_warning ("Failed to upload rumbling vibra effect: %s", g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
self->id = effect.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug("Playing rumbling vibra effect id %d", effect.id);
|
||||||
|
event.type = EV_FF;
|
||||||
|
event.value = 1;
|
||||||
|
event.code = self->id;
|
||||||
|
|
||||||
|
if (write (self->fd, (const void*) &event, sizeof (event)) < 0) {
|
||||||
|
g_warning ("Failed to play rumbling vibra effect.");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: fall back to multiple rumbles when sine not supported */
|
||||||
|
gboolean
|
||||||
|
fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude,
|
||||||
|
guint fade_in_level, guint fade_in_time)
|
||||||
|
{
|
||||||
|
struct input_event event;
|
||||||
|
struct ff_effect effect = { 0 };
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||||
|
|
||||||
|
if (!magnitude)
|
||||||
|
magnitude = 0x7FFF;
|
||||||
|
|
||||||
|
if (!fade_in_level)
|
||||||
|
fade_in_level = magnitude;
|
||||||
|
|
||||||
|
if (!fade_in_time)
|
||||||
|
fade_in_time = duration;
|
||||||
|
|
||||||
|
effect.type = FF_PERIODIC;
|
||||||
|
effect.id = -1;
|
||||||
|
effect.u.periodic.waveform = FF_SINE;
|
||||||
|
effect.u.periodic.period = 10;
|
||||||
|
effect.u.periodic.magnitude = magnitude;
|
||||||
|
effect.u.periodic.offset = 0;
|
||||||
|
effect.u.periodic.phase = 0;
|
||||||
|
effect.direction = 0x4000;
|
||||||
|
effect.u.periodic.envelope.attack_length = fade_in_time;
|
||||||
|
effect.u.periodic.envelope.attack_level = fade_in_level;;
|
||||||
|
effect.u.periodic.envelope.fade_length = 0;
|
||||||
|
effect.u.periodic.envelope.fade_level = 0;
|
||||||
|
effect.trigger.button = 0;
|
||||||
|
effect.trigger.interval = 0;
|
||||||
|
effect.replay.length = duration;
|
||||||
|
effect.replay.delay = 200;
|
||||||
|
|
||||||
|
g_debug("Uploading periodic effect (%d)", self->fd);
|
||||||
|
if (ioctl(self->fd, EVIOCSFF, &effect) == -1) {
|
||||||
|
g_warning ("Failed to upload periodic vibra effect: %s", g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug("Playing periodic vibra effect id %d", effect.id);
|
||||||
|
event.type = EV_FF;
|
||||||
|
self->id = event.code = effect.id;
|
||||||
|
event.value = 1;
|
||||||
|
|
||||||
|
if (write (self->fd, (const void*) &event, sizeof (event)) < 0) {
|
||||||
|
g_warning ("Failed to play rumbling effect.");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_dev_vibra_remove_effect (FbdDevVibra *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||||
|
|
||||||
|
g_debug("Erasing vibra effect (%d)", self->fd);
|
||||||
|
if (ioctl(self->fd, EVIOCRMFF, self->id) == -1) {
|
||||||
|
g_warning ("Failed to erase vibra effect with id %d: %s", self->id, strerror(errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_dev_vibra_stop(FbdDevVibra *self)
|
||||||
|
{
|
||||||
|
struct input_event stop = { 0 };
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||||
|
|
||||||
|
stop.type = EV_FF;
|
||||||
|
stop.code = self->id;
|
||||||
|
stop.value = 0;
|
||||||
|
|
||||||
|
if (write(self->fd, (const void*) &stop, sizeof(stop)) < 0) {
|
||||||
|
g_warning ("Failed to stop vibra effect with id %d: %s", self->id, strerror(errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fbd_dev_vibra_remove_effect (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUdevDevice *
|
||||||
|
fbd_dev_vibra_get_device(FbdDevVibra *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_DEV_VIBRA (self), FALSE);
|
||||||
|
|
||||||
|
return self->device;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gudev/gudev.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_DEV_VIBRA (fbd_dev_vibra_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdDevVibra, fbd_dev_vibra, FBD, DEV_VIBRA, GObject);
|
||||||
|
|
||||||
|
FbdDevVibra *fbd_dev_vibra_new (GUdevDevice *device, GError **error);
|
||||||
|
gboolean fbd_dev_vibra_rumble (FbdDevVibra *device, guint duration, gboolean upload);
|
||||||
|
gboolean fbd_dev_vibra_periodic (FbdDevVibra *self, guint duration, guint magnitude,
|
||||||
|
guint fade_in_level, guint fade_in_time);
|
||||||
|
gboolean fbd_dev_vibra_stop (FbdDevVibra *self);
|
||||||
|
gboolean fbd_dev_vibra_remove_effect (FbdDevVibra *self);
|
||||||
|
GUdevDevice *fbd_dev_vibra_get_device(FbdDevVibra *self);
|
||||||
|
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
+449
@@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-event"
|
||||||
|
|
||||||
|
#include "lfb-names.h"
|
||||||
|
#include "fbd.h"
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-event.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SIGNAL_FEEDBACKS_ENDED,
|
||||||
|
N_SIGNALS
|
||||||
|
};
|
||||||
|
static guint signals[N_SIGNALS];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_ID,
|
||||||
|
PROP_APP_ID,
|
||||||
|
PROP_EVENT,
|
||||||
|
PROP_END_REASON,
|
||||||
|
PROP_FEEDBACKS_ENDED,
|
||||||
|
PROP_TIMEOUT,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdEvent {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
guint id;
|
||||||
|
char *app_id;
|
||||||
|
char *event;
|
||||||
|
|
||||||
|
gint timeout;
|
||||||
|
gboolean expired;
|
||||||
|
guint timeout_id;
|
||||||
|
|
||||||
|
gboolean ended;
|
||||||
|
FbdEventEndReason end_reason;
|
||||||
|
|
||||||
|
GSList *feedbacks;
|
||||||
|
} FbdEvent;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FbdEvent, fbd_event, G_TYPE_OBJECT);
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_timeout_expired (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), G_SOURCE_REMOVE);
|
||||||
|
|
||||||
|
self->expired = TRUE;
|
||||||
|
self->timeout_id = 0;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
check_ended (FbdEvent *self)
|
||||||
|
{
|
||||||
|
if (!fbd_event_get_feedbacks_ended (self))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
self->ended = TRUE;
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FEEDBACKS_ENDED]);
|
||||||
|
g_signal_emit (self, signals[SIGNAL_FEEDBACKS_ENDED], 0);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_fb_ended (FbdEvent *self, FbdFeedbackBase *fb)
|
||||||
|
{
|
||||||
|
switch (self->timeout) {
|
||||||
|
case FBD_EVENT_TIMEOUT_ONESHOT:
|
||||||
|
check_ended (self);
|
||||||
|
break;
|
||||||
|
case FBD_EVENT_TIMEOUT_LOOP:
|
||||||
|
if (self->end_reason != FBD_EVENT_END_REASON_NATURAL)
|
||||||
|
check_ended (self);
|
||||||
|
else
|
||||||
|
fbd_feedback_run (fb);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!self->expired && self->end_reason == FBD_EVENT_END_REASON_NATURAL)
|
||||||
|
fbd_feedback_run (fb);
|
||||||
|
else
|
||||||
|
check_ended (self);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdEvent *self = FBD_EVENT (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_ID:
|
||||||
|
self->id = g_value_get_int (value);
|
||||||
|
break;
|
||||||
|
case PROP_APP_ID:
|
||||||
|
g_free (self->app_id);
|
||||||
|
self->app_id = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
case PROP_EVENT:
|
||||||
|
g_free (self->event);
|
||||||
|
self->event = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
case PROP_TIMEOUT:
|
||||||
|
self->timeout = g_value_get_int (value);
|
||||||
|
break;
|
||||||
|
case PROP_END_REASON:
|
||||||
|
fbd_event_set_end_reason (self, g_value_get_enum (value));
|
||||||
|
break;
|
||||||
|
case PROP_FEEDBACKS_ENDED:
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdEvent *self = FBD_EVENT (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_ID:
|
||||||
|
g_value_set_int (value, self->id);
|
||||||
|
break;
|
||||||
|
case PROP_APP_ID:
|
||||||
|
g_value_set_string (value, self->app_id);
|
||||||
|
break;
|
||||||
|
case PROP_EVENT:
|
||||||
|
g_value_set_string (value, self->event);
|
||||||
|
break;
|
||||||
|
case PROP_TIMEOUT:
|
||||||
|
g_value_set_int (value, self->timeout);
|
||||||
|
break;
|
||||||
|
case PROP_END_REASON:
|
||||||
|
g_value_set_enum (value, fbd_event_get_end_reason (self));
|
||||||
|
break;
|
||||||
|
case PROP_FEEDBACKS_ENDED:
|
||||||
|
g_value_set_boolean (value, self->ended);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdEvent *self = FBD_EVENT (object);
|
||||||
|
|
||||||
|
g_clear_handle_id (&self->timeout_id, g_source_remove);
|
||||||
|
|
||||||
|
if (self->feedbacks) {
|
||||||
|
/* Feedbacks end themselves when unrefed */
|
||||||
|
g_slist_free_full (self->feedbacks, g_object_unref);
|
||||||
|
self->feedbacks = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_event_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
FbdEvent *self = FBD_EVENT (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->app_id, g_free);
|
||||||
|
g_clear_pointer (&self->event, g_free);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_event_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_class_init (FbdEventClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_event_set_property;
|
||||||
|
object_class->get_property = fbd_event_get_property;
|
||||||
|
|
||||||
|
object_class->dispose = fbd_event_dispose;
|
||||||
|
object_class->finalize = fbd_event_finalize;
|
||||||
|
|
||||||
|
props[PROP_ID] =
|
||||||
|
g_param_spec_int (
|
||||||
|
"id",
|
||||||
|
"Id",
|
||||||
|
"The event id",
|
||||||
|
0, G_MAXINT, 0,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_APP_ID] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"app-id",
|
||||||
|
"App id",
|
||||||
|
"The application id",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_EVENT] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"event",
|
||||||
|
"Event",
|
||||||
|
"The event name",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_TIMEOUT] =
|
||||||
|
g_param_spec_int (
|
||||||
|
"timeout",
|
||||||
|
"Timeout",
|
||||||
|
"Timeout after which feedback for event should end",
|
||||||
|
-1, G_MAXINT, -1,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_FEEDBACKS_ENDED] =
|
||||||
|
g_param_spec_boolean (
|
||||||
|
"feedbacks-ended",
|
||||||
|
"Feedbacks ended",
|
||||||
|
"Whether all feedbacks have ended playing",
|
||||||
|
FALSE,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
props[PROP_END_REASON] =
|
||||||
|
g_param_spec_enum (
|
||||||
|
"end-reason",
|
||||||
|
"End reason",
|
||||||
|
"The reason why the feedbacks ends/ended",
|
||||||
|
FBD_TYPE_EVENT_END_REASON,
|
||||||
|
FBD_EVENT_END_REASON_NATURAL,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FbdEvent::feedbacks-ended:
|
||||||
|
*
|
||||||
|
* Emitted when all feedbacks associated with this event ended.
|
||||||
|
*/
|
||||||
|
signals[SIGNAL_FEEDBACKS_ENDED] = g_signal_new ("feedbacks-ended",
|
||||||
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_event_init (FbdEvent *self)
|
||||||
|
{
|
||||||
|
self->timeout = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdEvent *
|
||||||
|
fbd_event_new (gint id, const gchar *app_id, const gchar *event, gint timeout)
|
||||||
|
{
|
||||||
|
return FBD_EVENT (g_object_new (FBD_TYPE_EVENT,
|
||||||
|
"id", id,
|
||||||
|
"app_id", app_id,
|
||||||
|
"event", event,
|
||||||
|
"timeout", timeout,
|
||||||
|
NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
const gchar *
|
||||||
|
fbd_event_get_event (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
|
||||||
|
|
||||||
|
return self->event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gchar *
|
||||||
|
fbd_event_get_app_id (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), NULL);
|
||||||
|
|
||||||
|
return self->app_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint
|
||||||
|
fbd_event_get_id (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), 0);
|
||||||
|
|
||||||
|
return self->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
fbd_event_get_timeout (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), -1);
|
||||||
|
|
||||||
|
return self->timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_add_feedback:
|
||||||
|
* @self: The event that gets a feedback added
|
||||||
|
* @feedback: (transfer-none): The feedback to add
|
||||||
|
*
|
||||||
|
* Add a feedback to the list of feedbacks triggered by event
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_event_add_feedback (FbdEvent *self, FbdFeedbackBase *feedback)
|
||||||
|
{
|
||||||
|
self->feedbacks = g_slist_prepend (self->feedbacks, g_object_ref(feedback));
|
||||||
|
g_object_set_data (G_OBJECT (feedback), "event-id", GUINT_TO_POINTER(self->id));
|
||||||
|
g_signal_connect_object (feedback,
|
||||||
|
"ended",
|
||||||
|
(GCallback) on_fb_ended,
|
||||||
|
self,
|
||||||
|
G_CONNECT_SWAPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
GSList *
|
||||||
|
fbd_event_get_feedbacks (FbdEvent *self)
|
||||||
|
{
|
||||||
|
return self->feedbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
fbd_event_remove_feedback (FbdEvent *self, FbdFeedbackBase *feedback)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), 0);
|
||||||
|
|
||||||
|
self->feedbacks = g_slist_remove (self->feedbacks, feedback);
|
||||||
|
return g_slist_length (self->feedbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_run_feedbacks:
|
||||||
|
* @self: The Event
|
||||||
|
*
|
||||||
|
* Run all feedbacks associated for an event.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_event_run_feedbacks (FbdEvent *self)
|
||||||
|
{
|
||||||
|
GSList *l;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_EVENT (self));
|
||||||
|
|
||||||
|
g_debug ("Running %d feedbacks for event %d", g_slist_length (self->feedbacks), self->id);
|
||||||
|
|
||||||
|
if (!self->feedbacks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (self->timeout > 0) {
|
||||||
|
self->timeout_id = g_timeout_add_seconds (self->timeout,
|
||||||
|
(GSourceFunc)on_timeout_expired,
|
||||||
|
self);
|
||||||
|
g_source_set_name_by_id (self->timeout_id, "event timeout source");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (l = self->feedbacks; l; l = l->next) {
|
||||||
|
FbdFeedbackBase *fb = FBD_FEEDBACK_BASE (l->data);
|
||||||
|
fbd_feedback_run (fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_end_feedbacks:
|
||||||
|
* @self: The Event
|
||||||
|
*
|
||||||
|
* End all running feedbacks as early as possible.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_event_end_feedbacks (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_if_fail (FBD_IS_EVENT (self));
|
||||||
|
|
||||||
|
fbd_event_set_end_reason (self, FBD_EVENT_END_REASON_EXPLICIT);
|
||||||
|
g_debug ("Ending %d feedbacks for event %d", g_slist_length (self->feedbacks), self->id);
|
||||||
|
g_slist_foreach (self->feedbacks, (GFunc)fbd_feedback_end, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_get_feedbacks_ended:
|
||||||
|
* @self: The Event
|
||||||
|
*
|
||||||
|
* Whether all feedbacks have finished running.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if all feedbacks have finished, otherwise %FALSE.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
fbd_event_get_feedbacks_ended (FbdEvent *self)
|
||||||
|
{
|
||||||
|
GSList *l;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), FALSE);
|
||||||
|
|
||||||
|
if (!self->feedbacks)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
for (l = self->feedbacks; l ; l = l->next) {
|
||||||
|
if (!fbd_feedback_get_ended (FBD_FEEDBACK_BASE (l->data)))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_set_end_reason:
|
||||||
|
* @self: The Event
|
||||||
|
* @reason: The reason why feedback for the event ended
|
||||||
|
*
|
||||||
|
* Sets the reason why feedback for the event ends/has ended.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_event_set_end_reason (FbdEvent *self, FbdEventEndReason reason)
|
||||||
|
{
|
||||||
|
g_return_if_fail (FBD_IS_EVENT (self));
|
||||||
|
|
||||||
|
if (self->end_reason == reason)
|
||||||
|
return;
|
||||||
|
self->end_reason = reason;
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_END_REASON]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_event_get_end_reason:
|
||||||
|
* @self: The Event
|
||||||
|
*
|
||||||
|
* Returns: The reason why feedback for the event ended.
|
||||||
|
*/
|
||||||
|
FbdEventEndReason
|
||||||
|
fbd_event_get_end_reason (FbdEvent *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_EVENT (self), FBD_EVENT_END_REASON_NATURAL);
|
||||||
|
|
||||||
|
return self->end_reason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
typedef enum _FbdEventEndReason {
|
||||||
|
/* No feedback in current theme for this event */
|
||||||
|
FBD_EVENT_END_REASON_NOT_FOUND = -1,
|
||||||
|
/* all feedbacks finished playing their natural length */
|
||||||
|
FBD_EVENT_END_REASON_NATURAL = 0,
|
||||||
|
/* The timer expired */
|
||||||
|
FBD_EVENT_END_REASON_EXPIRED = 1,
|
||||||
|
/* Application wanted to end feedbacks explicitly */
|
||||||
|
FBD_EVENT_END_REASON_EXPLICIT = 2,
|
||||||
|
} FbdEventEndReason;
|
||||||
|
|
||||||
|
typedef enum _FbdEventTimeout {
|
||||||
|
/* Run each feedback once */
|
||||||
|
FBD_EVENT_TIMEOUT_ONESHOT = -1,
|
||||||
|
FBD_EVENT_TIMEOUT_LOOP = 0,
|
||||||
|
} FbdEventTimeout;
|
||||||
|
|
||||||
|
#define FBD_TYPE_EVENT (fbd_event_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdEvent, fbd_event, FBD, EVENT, GObject);
|
||||||
|
|
||||||
|
FbdEvent *fbd_event_new (gint id, const gchar *app_id, const gchar *event, gint timeout);
|
||||||
|
const gchar *fbd_event_get_event (FbdEvent *event);
|
||||||
|
const gchar *fbd_event_get_app_id (FbdEvent *event);
|
||||||
|
guint fbd_event_get_id (FbdEvent *event);
|
||||||
|
gint fbd_event_get_timeout (FbdEvent *self);
|
||||||
|
void fbd_event_set_end_reason (FbdEvent *self, FbdEventEndReason reason);
|
||||||
|
FbdEventEndReason fbd_event_get_end_reason (FbdEvent *self);
|
||||||
|
GSList * fbd_event_get_feedbacks (FbdEvent *self);
|
||||||
|
void fbd_event_add_feedback (FbdEvent *self,
|
||||||
|
FbdFeedbackBase *feedback);
|
||||||
|
gint fbd_event_remove_feedback (FbdEvent *self,
|
||||||
|
FbdFeedbackBase *feedback);
|
||||||
|
void fbd_event_run_feedbacks (FbdEvent *self);
|
||||||
|
void fbd_event_end_feedbacks (FbdEvent *self);
|
||||||
|
gboolean fbd_event_get_feedbacks_ended (FbdEvent *self);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-base"
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-base
|
||||||
|
* @short_description: Base class for different feedback types
|
||||||
|
* @Title: FbdFeedbackManager
|
||||||
|
*
|
||||||
|
* You usually don't want to create objects of this type. It just
|
||||||
|
* serves as a base class for other feedback types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_EVENT_NAME,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SIGNAL_ENDED,
|
||||||
|
N_SIGNALS
|
||||||
|
};
|
||||||
|
static guint signals[N_SIGNALS];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackBasePrivate {
|
||||||
|
gchar *event_name;
|
||||||
|
gboolean ended;
|
||||||
|
} FbdFeedbackBasePrivate;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_PRIVATE (FbdFeedbackBase, fbd_feedback_base, G_TYPE_OBJECT);
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||||
|
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EVENT_NAME:
|
||||||
|
g_free (priv->event_name);
|
||||||
|
priv->event_name = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||||
|
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EVENT_NAME:
|
||||||
|
g_value_set_string (value, priv->event_name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||||
|
|
||||||
|
/* end feedback if running */
|
||||||
|
if (!fbd_feedback_get_ended (self))
|
||||||
|
fbd_feedback_end (self);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_base_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackBase *self = FBD_FEEDBACK_BASE (object);
|
||||||
|
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
g_clear_pointer (&priv->event_name, g_free);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_base_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_class_init (FbdFeedbackBaseClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_base_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_base_get_property;
|
||||||
|
|
||||||
|
object_class->dispose = fbd_feedback_base_dispose;
|
||||||
|
object_class->finalize = fbd_feedback_base_finalize;
|
||||||
|
|
||||||
|
props[PROP_EVENT_NAME] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"event-name",
|
||||||
|
"Event Name",
|
||||||
|
"The event this feedback is associated with",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FbdFeedbackBase::ended:
|
||||||
|
*
|
||||||
|
* Emitted when the feedback has ended
|
||||||
|
*/
|
||||||
|
signals[SIGNAL_ENDED] = g_signal_new ("ended",
|
||||||
|
G_TYPE_FROM_CLASS (klass),
|
||||||
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_base_init (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_feedback_get_event_name:
|
||||||
|
* @self: The feedback
|
||||||
|
*
|
||||||
|
* Returns: the name from the event naming spec this feedback is associated with.
|
||||||
|
*/
|
||||||
|
const gchar *
|
||||||
|
fbd_feedback_get_event_name (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackBasePrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_BASE (self), NULL);
|
||||||
|
priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
return priv->event_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_feedback_run:
|
||||||
|
* @self: The feedback to run
|
||||||
|
*
|
||||||
|
* Emit the feedback.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_feedback_run (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackBaseClass *klass;
|
||||||
|
FbdFeedbackBasePrivate *priv;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_BASE (self));
|
||||||
|
priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
priv->ended = FALSE;
|
||||||
|
klass = FBD_FEEDBACK_BASE_GET_CLASS (self);
|
||||||
|
g_return_if_fail (klass->run);
|
||||||
|
klass->run (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_feedback_end:
|
||||||
|
* @self: The feedback to end
|
||||||
|
*
|
||||||
|
* End the feedback immediately.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_feedback_end (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackBaseClass *klass;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_BASE (self));
|
||||||
|
|
||||||
|
klass = FBD_FEEDBACK_BASE_GET_CLASS (self);
|
||||||
|
g_return_if_fail (klass->end);
|
||||||
|
klass->end (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_feedback_get_ended:
|
||||||
|
* @self: The feedback
|
||||||
|
*
|
||||||
|
* Whether the feedback is ended.
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if feedback has ended, otherwise %FALSE.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
fbd_feedback_get_ended (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
return priv->ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fbd_feedback_base_done:
|
||||||
|
* @self: The feedback
|
||||||
|
*
|
||||||
|
* Invoked by a derived classes to notify that it's done emitting feedback,
|
||||||
|
* e.g. when the vibra motor stopped or a sound finished playing.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
fbd_feedback_base_done (FbdFeedbackBase *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackBasePrivate *priv = fbd_feedback_base_get_instance_private (self);
|
||||||
|
|
||||||
|
priv->ended = TRUE;
|
||||||
|
g_signal_emit (self, signals[SIGNAL_ENDED], 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_BASE (fbd_feedback_base_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_DERIVABLE_TYPE (FbdFeedbackBase, fbd_feedback_base, FBD, FEEDBACK_BASE, GObject);
|
||||||
|
|
||||||
|
struct _FbdFeedbackBaseClass
|
||||||
|
{
|
||||||
|
GObjectClass parent_class;
|
||||||
|
|
||||||
|
void (*run) (FbdFeedbackBase *self);
|
||||||
|
void (*end) (FbdFeedbackBase *self);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gchar *fbd_feedback_get_event_name (FbdFeedbackBase *self);
|
||||||
|
void fbd_feedback_run (FbdFeedbackBase *self);
|
||||||
|
void fbd_feedback_end (FbdFeedbackBase *self);
|
||||||
|
gboolean fbd_feedback_get_ended (FbdFeedbackBase *self);
|
||||||
|
void fbd_feedback_base_done (FbdFeedbackBase *self);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-dummy"
|
||||||
|
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-dummy
|
||||||
|
* @short_description: Describes a dummy feedback event that does nothing
|
||||||
|
* @Title: FbdFeedbackDummy
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_DURATION,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackDummy {
|
||||||
|
FbdFeedbackBase parent;
|
||||||
|
|
||||||
|
guint duration;
|
||||||
|
guint timer_id;
|
||||||
|
} FbdFeedbackDummy;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FbdFeedbackDummy, fbd_feedback_dummy, FBD_TYPE_FEEDBACK_BASE);
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_DURATION:
|
||||||
|
self->duration = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_DURATION:
|
||||||
|
g_value_set_uint (value, self->duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_timeout_expired (FbdFeedbackDummy *self)
|
||||||
|
{
|
||||||
|
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_run (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (base);
|
||||||
|
|
||||||
|
if (self->duration) {
|
||||||
|
self->timer_id = g_timeout_add (self->duration,
|
||||||
|
(GSourceFunc)on_timeout_expired,
|
||||||
|
self);
|
||||||
|
g_source_set_name_by_id (self->timer_id, "feedback-dummy-timer");
|
||||||
|
} else {
|
||||||
|
fbd_feedback_base_done (base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_end (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackDummy *self = FBD_FEEDBACK_DUMMY (base);
|
||||||
|
|
||||||
|
g_clear_handle_id(&self->timer_id, g_source_remove);
|
||||||
|
fbd_feedback_base_done (base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_class_init (FbdFeedbackDummyClass *klass)
|
||||||
|
{
|
||||||
|
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_dummy_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_dummy_get_property;
|
||||||
|
|
||||||
|
base_class->run = fbd_feedback_dummy_run;
|
||||||
|
base_class->end = fbd_feedback_dummy_end;
|
||||||
|
|
||||||
|
props[PROP_DURATION] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"duration",
|
||||||
|
"Duration",
|
||||||
|
"Dummy event duration in msecs",
|
||||||
|
0, G_MAXUINT, 0,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_dummy_init (FbdFeedbackDummy *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_DUMMY (fbd_feedback_dummy_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackDummy, fbd_feedback_dummy, FBD, FEEDBACK_DUMMY, FbdFeedbackBase);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,394 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-manager"
|
||||||
|
|
||||||
|
#include "lfb-names.h"
|
||||||
|
#include "fbd.h"
|
||||||
|
#include "fbd-dev-vibra.h"
|
||||||
|
#include "fbd-event.h"
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
#include "fbd-feedback-theme.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib-unix.h>
|
||||||
|
#include <gudev/gudev.h>
|
||||||
|
|
||||||
|
#define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd"
|
||||||
|
#define FEEDBACKD_KEY_PROFILE "profile"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-manager
|
||||||
|
* @short_description: The manager processing incoming events
|
||||||
|
* @Title: FbdFeedbackManager
|
||||||
|
*
|
||||||
|
* The #FbdFeedbackManager listens for DBus messages and triggers feedbacks
|
||||||
|
* based on the incoming events.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackManager {
|
||||||
|
LfbGdbusFeedbackSkeleton parent;
|
||||||
|
|
||||||
|
GSettings *settings;
|
||||||
|
FbdFeedbackProfileLevel level;
|
||||||
|
FbdFeedbackTheme *theme;
|
||||||
|
guint next_id;
|
||||||
|
|
||||||
|
GHashTable *events;
|
||||||
|
|
||||||
|
/* Hardware interaction */
|
||||||
|
GUdevClient *client;
|
||||||
|
FbdDevVibra *vibra;
|
||||||
|
FbdDevSound *sound;
|
||||||
|
} FbdFeedbackManager;
|
||||||
|
|
||||||
|
static void fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackManager,
|
||||||
|
fbd_feedback_manager,
|
||||||
|
LFB_GDBUS_TYPE_FEEDBACK_SKELETON,
|
||||||
|
G_IMPLEMENT_INTERFACE (
|
||||||
|
LFB_GDBUS_TYPE_FEEDBACK,
|
||||||
|
fbd_feedback_manager_feedback_iface_init));
|
||||||
|
|
||||||
|
static void
|
||||||
|
device_changes (FbdFeedbackManager *self, gchar *action, GUdevDevice *device,
|
||||||
|
GUdevClient *client)
|
||||||
|
{
|
||||||
|
g_debug ("Device changes: action = %s, device = %s",
|
||||||
|
action, g_udev_device_get_sysfs_path (device));
|
||||||
|
|
||||||
|
if (g_strcmp0 (action, "remove") == 0 && self->vibra) {
|
||||||
|
GUdevDevice *dev = fbd_dev_vibra_get_device (self->vibra);
|
||||||
|
|
||||||
|
if (g_strcmp0 (g_udev_device_get_sysfs_path(dev),
|
||||||
|
g_udev_device_get_sysfs_path (device)) == 0) {
|
||||||
|
g_debug ("Vibra device %s got remove", g_udev_device_get_sysfs_path (dev));
|
||||||
|
g_clear_object (&self->vibra);
|
||||||
|
}
|
||||||
|
} else if (g_strcmp0 (action, "add") == 0 || !self->vibra) {
|
||||||
|
if (!g_strcmp0 (g_udev_device_get_property (device, "FEEDBACKD_TYPE"), "vibra")) {
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
|
||||||
|
g_debug ("Found hotplugged vibra device at %s", g_udev_device_get_sysfs_path (device));
|
||||||
|
self->vibra = fbd_dev_vibra_new (device, &err);
|
||||||
|
if (!self->vibra) {
|
||||||
|
g_warning ("Failed to init vibra device: %s", err->message);
|
||||||
|
g_clear_error (&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_devices (FbdFeedbackManager *self)
|
||||||
|
{
|
||||||
|
GList *devices, *l;
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
|
||||||
|
devices = g_udev_client_query_by_subsystem (self->client, "input");
|
||||||
|
|
||||||
|
for (l = devices; l != NULL; l = l->next) {
|
||||||
|
GUdevDevice *dev = l->data;
|
||||||
|
|
||||||
|
if (!g_strcmp0 (g_udev_device_get_property (dev, "FEEDBACKD_TYPE"), "vibra")) {
|
||||||
|
g_debug ("Found vibra device");
|
||||||
|
self->vibra = fbd_dev_vibra_new (dev, &err);
|
||||||
|
if (!self->vibra) {
|
||||||
|
g_warning ("Failed to init vibra device: %s", err->message);
|
||||||
|
g_clear_error (&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!self->vibra)
|
||||||
|
g_debug ("No vibra capable device found");
|
||||||
|
|
||||||
|
self->sound = fbd_dev_sound_new (&err);
|
||||||
|
if (!self->sound) {
|
||||||
|
g_warning ("Failed to init sound device: %s", err->message);
|
||||||
|
g_clear_error (&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_event_feedbacks_ended (FbdFeedbackManager *self, FbdEvent *event)
|
||||||
|
{
|
||||||
|
guint event_id;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||||
|
g_return_if_fail (FBD_IS_EVENT (event));
|
||||||
|
|
||||||
|
event_id = fbd_event_get_id (event);
|
||||||
|
event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
|
||||||
|
if (!event) {
|
||||||
|
g_warning ("Feedback ended for unknown event %d", event_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_return_if_fail (fbd_event_get_feedbacks_ended (event));
|
||||||
|
|
||||||
|
lfb_gdbus_feedback_emit_feedback_ended
|
||||||
|
(LFB_GDBUS_FEEDBACK(self), event_id,
|
||||||
|
fbd_event_get_end_reason (event));
|
||||||
|
|
||||||
|
g_debug ("All feedbacks for event %d finished", event_id);
|
||||||
|
g_hash_table_remove (self->events, GUINT_TO_POINTER (event_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_profile_changed (FbdFeedbackManager *self, GParamSpec *psepc, gpointer unused)
|
||||||
|
{
|
||||||
|
const gchar *pname;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||||
|
|
||||||
|
pname = lfb_gdbus_feedback_get_profile (LFB_GDBUS_FEEDBACK (self));
|
||||||
|
|
||||||
|
if (!fbd_feedback_manager_set_profile (self, pname))
|
||||||
|
g_warning ("Invalid profile '%s'", pname);
|
||||||
|
|
||||||
|
/* TODO: end running feedbacks that aren't allowed in new profile immediately */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_feedbackd_setting_changed (FbdFeedbackManager *self,
|
||||||
|
const gchar *key,
|
||||||
|
GSettings *settings)
|
||||||
|
{
|
||||||
|
g_autofree gchar *profile = NULL;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_MANAGER (self));
|
||||||
|
g_return_if_fail (G_IS_SETTINGS (settings));
|
||||||
|
g_return_if_fail (!g_strcmp0 (key, FEEDBACKD_KEY_PROFILE));
|
||||||
|
|
||||||
|
profile = g_settings_get_string (settings, key);
|
||||||
|
fbd_feedback_manager_set_profile (self, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fbd_feedback_manager_handle_trigger_feedback (LfbGdbusFeedback *object,
|
||||||
|
GDBusMethodInvocation *invocation,
|
||||||
|
const gchar *arg_app_id,
|
||||||
|
const gchar *arg_event,
|
||||||
|
GVariant *arg_hints,
|
||||||
|
gint arg_timeout)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *self;
|
||||||
|
FbdEvent *event;
|
||||||
|
GSList *feedbacks, *l;
|
||||||
|
gint event_id;
|
||||||
|
|
||||||
|
g_debug ("Event '%s' for '%s'", arg_event, arg_app_id);
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
|
||||||
|
g_return_val_if_fail (arg_app_id, FALSE);
|
||||||
|
g_return_val_if_fail (arg_event, FALSE);
|
||||||
|
|
||||||
|
self = FBD_FEEDBACK_MANAGER (object);
|
||||||
|
if (!strlen (arg_app_id)) {
|
||||||
|
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||||
|
G_DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"Invalid app id %s", arg_app_id);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen (arg_event)) {
|
||||||
|
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
|
||||||
|
G_DBUS_ERROR_INVALID_ARGS,
|
||||||
|
"Invalid event %s", arg_event);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg_timeout < -1)
|
||||||
|
arg_timeout = -1;
|
||||||
|
|
||||||
|
event_id = self->next_id++;
|
||||||
|
|
||||||
|
event = fbd_event_new (event_id, arg_app_id, arg_event, arg_timeout);
|
||||||
|
g_hash_table_insert (self->events, GUINT_TO_POINTER (event_id), event);
|
||||||
|
|
||||||
|
feedbacks = fbd_feedback_theme_lookup_feedback (self->theme, self->level, event);
|
||||||
|
|
||||||
|
if (feedbacks) {
|
||||||
|
for (l = feedbacks; l; l = l->next) {
|
||||||
|
FbdFeedbackBase *fb = l->data;
|
||||||
|
fbd_event_add_feedback (event, fb);
|
||||||
|
g_object_unref (fb);
|
||||||
|
}
|
||||||
|
g_slist_free (feedbacks);
|
||||||
|
|
||||||
|
g_signal_connect_object (event, "feedbacks-ended",
|
||||||
|
(GCallback) on_event_feedbacks_ended,
|
||||||
|
self,
|
||||||
|
G_CONNECT_SWAPPED);
|
||||||
|
|
||||||
|
fbd_event_run_feedbacks (event);
|
||||||
|
} else {
|
||||||
|
/* No feedbacks found, so we're done */
|
||||||
|
lfb_gdbus_feedback_emit_feedback_ended
|
||||||
|
(LFB_GDBUS_FEEDBACK(self), event_id,
|
||||||
|
FBD_EVENT_END_REASON_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
lfb_gdbus_feedback_complete_trigger_feedback (object, invocation, event_id);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fbd_feedback_manager_handle_end_feedback (LfbGdbusFeedback *object,
|
||||||
|
GDBusMethodInvocation *invocation,
|
||||||
|
guint event_id)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *self;
|
||||||
|
FbdEvent *event;
|
||||||
|
|
||||||
|
g_debug ("Ending feedback for event '%d'", event_id);
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (object), FALSE);
|
||||||
|
g_return_val_if_fail (event_id, FALSE);
|
||||||
|
|
||||||
|
self = FBD_FEEDBACK_MANAGER (object);
|
||||||
|
|
||||||
|
event = g_hash_table_lookup (self->events, GUINT_TO_POINTER (event_id));
|
||||||
|
if (event) {
|
||||||
|
/* The last feedback ending will trigger event disposal via
|
||||||
|
`on_fb_ended` */
|
||||||
|
fbd_event_end_feedbacks (event);
|
||||||
|
} else {
|
||||||
|
g_warning ("Tried to end non-existing event %d", event_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
lfb_gdbus_feedback_complete_end_feedback (object, invocation);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_manager_constructed (GObject *object)
|
||||||
|
{
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
|
||||||
|
const gchar *themefile;
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->constructed (object);
|
||||||
|
|
||||||
|
themefile = g_getenv ("FEEDBACK_THEME");
|
||||||
|
if (!themefile)
|
||||||
|
themefile = FEEDBACKD_THEME_DIR "/default.json";
|
||||||
|
|
||||||
|
self->theme = fbd_feedback_theme_new_from_file (themefile, &err);
|
||||||
|
if (!self->theme) {
|
||||||
|
/* No point to carry on */
|
||||||
|
g_error ("Failed to load default theme: %s", err->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_connect (self, "notify::profile", (GCallback)on_profile_changed, NULL);
|
||||||
|
lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self),
|
||||||
|
fbd_feedback_profile_level_to_string(self->level));
|
||||||
|
|
||||||
|
self->settings = g_settings_new (FEEDBACKD_SCHEMA_ID);
|
||||||
|
g_signal_connect_swapped (self->settings, "changed::" FEEDBACKD_KEY_PROFILE,
|
||||||
|
G_CALLBACK (on_feedbackd_setting_changed), self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_manager_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *self = FBD_FEEDBACK_MANAGER (object);
|
||||||
|
|
||||||
|
g_clear_object (&self->settings);
|
||||||
|
g_clear_object (&self->theme);
|
||||||
|
g_clear_object (&self->vibra);
|
||||||
|
g_clear_object (&self->client);
|
||||||
|
g_clear_pointer (&self->events, g_hash_table_destroy);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_manager_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_manager_feedback_iface_init (LfbGdbusFeedbackIface *iface)
|
||||||
|
{
|
||||||
|
iface->handle_trigger_feedback = fbd_feedback_manager_handle_trigger_feedback;
|
||||||
|
iface->handle_end_feedback = fbd_feedback_manager_handle_end_feedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_manager_class_init (FbdFeedbackManagerClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->constructed = fbd_feedback_manager_constructed;
|
||||||
|
object_class->dispose = fbd_feedback_manager_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_manager_init (FbdFeedbackManager *self)
|
||||||
|
{
|
||||||
|
const gchar * const subsystems[] = { "input", NULL };
|
||||||
|
|
||||||
|
self->next_id = 1;
|
||||||
|
self->level = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
|
||||||
|
|
||||||
|
self->client = g_udev_client_new (subsystems);
|
||||||
|
g_signal_connect_swapped (G_OBJECT (self->client), "uevent",
|
||||||
|
G_CALLBACK (device_changes), self);
|
||||||
|
init_devices (self);
|
||||||
|
|
||||||
|
self->events = g_hash_table_new_full (g_direct_hash,
|
||||||
|
g_direct_equal,
|
||||||
|
NULL,
|
||||||
|
(GDestroyNotify)g_object_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackManager *
|
||||||
|
fbd_feedback_manager_get_default (void)
|
||||||
|
{
|
||||||
|
static FbdFeedbackManager *instance;
|
||||||
|
|
||||||
|
if (instance == NULL) {
|
||||||
|
instance = g_object_new (FBD_TYPE_FEEDBACK_MANAGER, NULL);
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdDevVibra *
|
||||||
|
fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
|
||||||
|
|
||||||
|
return self->vibra;
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdDevSound *
|
||||||
|
fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), NULL);
|
||||||
|
|
||||||
|
return self->sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfileLevel level;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_MANAGER (self), FALSE);
|
||||||
|
|
||||||
|
level = fbd_feedback_profile_level (profile);
|
||||||
|
|
||||||
|
if (level == FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (level == self->level)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
g_debug ("Switching profile to '%s'", profile);
|
||||||
|
self->level = level;
|
||||||
|
lfb_gdbus_feedback_set_profile (LFB_GDBUS_FEEDBACK (self), profile);
|
||||||
|
g_settings_set_string (self->settings, FEEDBACKD_KEY_PROFILE, profile);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-dev-vibra.h"
|
||||||
|
#include "fbd-dev-sound.h"
|
||||||
|
#include "lfb-gdbus.h"
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_MANAGER (fbd_feedback_manager_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackManager, fbd_feedback_manager, FBD, FEEDBACK_MANAGER, LfbGdbusFeedbackSkeleton);
|
||||||
|
|
||||||
|
FbdFeedbackManager *fbd_feedback_manager_get_default (void);
|
||||||
|
FbdDevVibra *fbd_feedback_manager_get_dev_vibra (FbdFeedbackManager *self);
|
||||||
|
FbdDevSound *fbd_feedback_manager_get_dev_sound (FbdFeedbackManager *self);
|
||||||
|
gboolean fbd_feedback_manager_set_profile (FbdFeedbackManager *self, const gchar *profile);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-profile"
|
||||||
|
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
#include "fbd-feedback-profile.h"
|
||||||
|
#include "fbd-feedback-sound.h"
|
||||||
|
#include "fbd-feedback-vibra-periodic.h"
|
||||||
|
#include "fbd-feedback-vibra-rumble.h"
|
||||||
|
|
||||||
|
#include <json-glib/json-glib.h>
|
||||||
|
|
||||||
|
#define FBD_FEEDBACK_CLS_PREFIX "FbdFeedback"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_NAME,
|
||||||
|
PROP_FEEDBACKS,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-profile
|
||||||
|
* @short_description: A single profile in a #FbdFeedback
|
||||||
|
* @Title: FbdFeedbackProfile
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackProfile {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
gchar *name;
|
||||||
|
GHashTable *feedbacks;
|
||||||
|
} FbdFeedbackProfile;
|
||||||
|
|
||||||
|
static void json_serializable_iface_init (JsonSerializableIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackProfile, fbd_feedback_profile, G_TYPE_OBJECT,
|
||||||
|
G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
|
||||||
|
json_serializable_iface_init));
|
||||||
|
|
||||||
|
|
||||||
|
static JsonNode *
|
||||||
|
fbd_feedback_profile_serializable_serialize_property (JsonSerializable *serializable,
|
||||||
|
const gchar *property_name,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (serializable);
|
||||||
|
JsonNode *node = NULL;
|
||||||
|
|
||||||
|
if (g_strcmp0 (property_name, "feedbacks") == 0) {
|
||||||
|
GHashTableIter iter;
|
||||||
|
gpointer key;
|
||||||
|
FbdFeedbackProfile *profile;
|
||||||
|
JsonArray *array = json_array_sized_new (FBD_FEEDBACK_PROFILE_N_PROFILES);
|
||||||
|
|
||||||
|
g_hash_table_iter_init (&iter, self->feedbacks);
|
||||||
|
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *) &profile)) {
|
||||||
|
json_array_add_element (array, json_gobject_serialize (G_OBJECT(profile)));
|
||||||
|
}
|
||||||
|
node = json_node_init_array (json_node_alloc (), array);
|
||||||
|
} else {
|
||||||
|
node = json_serializable_default_serialize_property (serializable,
|
||||||
|
property_name,
|
||||||
|
value,
|
||||||
|
pspec);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GType
|
||||||
|
feedback_get_type (JsonNode *feedback_node)
|
||||||
|
{
|
||||||
|
g_autofree gchar *type_name = NULL;
|
||||||
|
JsonObject *obj = json_node_get_object (feedback_node);
|
||||||
|
JsonNode *type_node = json_object_get_member (obj, "type");
|
||||||
|
GType gtype = 0;
|
||||||
|
|
||||||
|
/* Ensure all feedback types so the json parsing can use them */
|
||||||
|
g_type_ensure (FBD_TYPE_FEEDBACK_DUMMY);
|
||||||
|
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_PERIODIC);
|
||||||
|
g_type_ensure (FBD_TYPE_FEEDBACK_VIBRA_RUMBLE);
|
||||||
|
g_type_ensure (FBD_TYPE_FEEDBACK_SOUND);
|
||||||
|
|
||||||
|
g_return_val_if_fail (type_node, FBD_TYPE_FEEDBACK_DUMMY);
|
||||||
|
|
||||||
|
type_name = g_strdup_printf ("FbdFeedback%s",
|
||||||
|
json_node_get_string (type_node));
|
||||||
|
type_name[strlen(FBD_FEEDBACK_CLS_PREFIX)] =
|
||||||
|
g_ascii_toupper (type_name[strlen(FBD_FEEDBACK_CLS_PREFIX)]);
|
||||||
|
gtype = g_type_from_name (type_name);
|
||||||
|
|
||||||
|
g_debug ("Feedback %s, type %lu", type_name, gtype);
|
||||||
|
g_return_val_if_fail (gtype, FBD_TYPE_FEEDBACK_DUMMY);
|
||||||
|
return gtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fbd_feedback_profile_serializable_deserialize_property (JsonSerializable *serializable,
|
||||||
|
const gchar *property_name,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
JsonNode *property_node)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (property_name, "feedbacks") == 0) {
|
||||||
|
if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) {
|
||||||
|
g_value_set_pointer (value, NULL);
|
||||||
|
return TRUE;
|
||||||
|
} else if (JSON_NODE_TYPE (property_node) == JSON_NODE_ARRAY) {
|
||||||
|
JsonArray *array = json_node_get_array (property_node);
|
||||||
|
guint i, array_len = json_array_get_length (array);
|
||||||
|
GHashTable *feedbacks = g_hash_table_new_full (g_str_hash,
|
||||||
|
g_str_equal,
|
||||||
|
g_free,
|
||||||
|
(GDestroyNotify)g_object_unref);
|
||||||
|
for (i = 0; i < array_len; i++) {
|
||||||
|
JsonNode *element_node = json_array_get_element (array, i);
|
||||||
|
|
||||||
|
if (JSON_NODE_HOLDS_OBJECT (element_node)) {
|
||||||
|
gchar *event_name;
|
||||||
|
FbdFeedbackBase *feedback;
|
||||||
|
GType gtype = feedback_get_type (element_node);
|
||||||
|
|
||||||
|
feedback = FBD_FEEDBACK_BASE (json_gobject_deserialize (gtype, element_node));
|
||||||
|
event_name = g_strdup (fbd_feedback_get_event_name (FBD_FEEDBACK_BASE(feedback)));
|
||||||
|
g_hash_table_insert (feedbacks, event_name, feedback);
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_value_set_boxed (value, feedbacks);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
} else {
|
||||||
|
return json_serializable_default_deserialize_property (serializable,
|
||||||
|
property_name,
|
||||||
|
value,
|
||||||
|
pspec,
|
||||||
|
property_node);
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_NAME:
|
||||||
|
g_free (self->name);
|
||||||
|
self->name = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
case PROP_FEEDBACKS:
|
||||||
|
if (self->feedbacks)
|
||||||
|
g_hash_table_unref (self->feedbacks);
|
||||||
|
self->feedbacks = g_value_get_boxed (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_NAME:
|
||||||
|
g_value_set_string (value, self->name);
|
||||||
|
break;
|
||||||
|
case PROP_FEEDBACKS:
|
||||||
|
g_value_set_boxed (value, self->feedbacks);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->feedbacks, g_hash_table_unref);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->name, g_free);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
json_serializable_iface_init (JsonSerializableIface *iface)
|
||||||
|
{
|
||||||
|
iface->serialize_property = fbd_feedback_profile_serializable_serialize_property;
|
||||||
|
iface->deserialize_property = fbd_feedback_profile_serializable_deserialize_property;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_constructed (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfile *self = FBD_FEEDBACK_PROFILE (object);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_profile_parent_class)->constructed (object);
|
||||||
|
|
||||||
|
if (!self->feedbacks) {
|
||||||
|
self->feedbacks = g_hash_table_new_full (g_str_hash,
|
||||||
|
g_str_equal,
|
||||||
|
g_free,
|
||||||
|
(GDestroyNotify)g_object_unref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_class_init (FbdFeedbackProfileClass *klass)
|
||||||
|
{
|
||||||
|
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_profile_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_profile_get_property;
|
||||||
|
|
||||||
|
object_class->constructed = fbd_feedback_profile_constructed;
|
||||||
|
object_class->dispose = fbd_feedback_profile_dispose;
|
||||||
|
object_class->finalize = fbd_feedback_profile_finalize;
|
||||||
|
|
||||||
|
props[PROP_NAME] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"name",
|
||||||
|
"Name",
|
||||||
|
"The feedback name",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_FEEDBACKS] =
|
||||||
|
g_param_spec_boxed (
|
||||||
|
"feedbacks",
|
||||||
|
"Feedbacks",
|
||||||
|
"The feedbacks for this profile",
|
||||||
|
G_TYPE_HASH_TABLE,
|
||||||
|
/* Can't be CONSTRUCT_ONLY since json-glib can't handle it */
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_profile_init (FbdFeedbackProfile *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackProfile *
|
||||||
|
fbd_feedback_profile_new (const gchar *name)
|
||||||
|
{
|
||||||
|
return g_object_new(FBD_TYPE_FEEDBACK_PROFILE,
|
||||||
|
"name", name,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
fbd_feedback_profile_get_name (FbdFeedbackProfile *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_PROFILE (self), "");
|
||||||
|
|
||||||
|
return self->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fbd_feedback_profile_add_feedback (FbdFeedbackProfile *self, FbdFeedbackBase *feedback)
|
||||||
|
{
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (self));
|
||||||
|
gchar *name = g_strdup (fbd_feedback_get_event_name (feedback));
|
||||||
|
|
||||||
|
/* TODO: allow for more than one feedback per event and profile */
|
||||||
|
g_hash_table_insert (self->feedbacks, name, g_object_ref (feedback));
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackBase *
|
||||||
|
fbd_feedback_profile_get_feedback (FbdFeedbackProfile *self, const char *event_name)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_PROFILE (self), NULL);
|
||||||
|
|
||||||
|
return g_hash_table_lookup (self->feedbacks, event_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackProfileLevel
|
||||||
|
fbd_feedback_profile_level (const char *name)
|
||||||
|
{
|
||||||
|
FbdFeedbackProfileLevel profile = FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN;
|
||||||
|
|
||||||
|
if (!g_strcmp0("silent", name)) {
|
||||||
|
profile = FBD_FEEDBACK_PROFILE_LEVEL_SILENT;
|
||||||
|
} else if (!g_strcmp0("quiet", name)) {
|
||||||
|
profile = FBD_FEEDBACK_PROFILE_LEVEL_QUIET;
|
||||||
|
} else if (!g_strcmp0("full", name)) {
|
||||||
|
profile = FBD_FEEDBACK_PROFILE_LEVEL_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
fbd_feedback_profile_level_to_string (FbdFeedbackProfileLevel level)
|
||||||
|
{
|
||||||
|
switch (level) {
|
||||||
|
case FBD_FEEDBACK_PROFILE_LEVEL_SILENT:
|
||||||
|
return "silent";
|
||||||
|
case FBD_FEEDBACK_PROFILE_LEVEL_QUIET:
|
||||||
|
return "quiet";
|
||||||
|
case FBD_FEEDBACK_PROFILE_LEVEL_FULL:
|
||||||
|
return "full";
|
||||||
|
case FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN:
|
||||||
|
default:
|
||||||
|
return "full";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fbd-feedback-base.h>
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
typedef enum _FbdFeedbackProfileLevel {
|
||||||
|
FBD_FEEDBACK_PROFILE_LEVEL_UNKNOWN = -1,
|
||||||
|
FBD_FEEDBACK_PROFILE_LEVEL_SILENT = 0,
|
||||||
|
FBD_FEEDBACK_PROFILE_LEVEL_QUIET,
|
||||||
|
FBD_FEEDBACK_PROFILE_LEVEL_FULL,
|
||||||
|
FBD_FEEDBACK_PROFILE_N_PROFILES,
|
||||||
|
} FbdFeedbackProfileLevel;
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_PROFILE (fbd_feedback_profile_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackProfile, fbd_feedback_profile, FBD, FEEDBACK_PROFILE, GObject);
|
||||||
|
|
||||||
|
FbdFeedbackProfile *fbd_feedback_profile_new (const gchar *name);
|
||||||
|
const gchar *fbd_feedback_profile_get_name (FbdFeedbackProfile *self);
|
||||||
|
void fbd_feedback_profile_add_feedback (FbdFeedbackProfile *self,
|
||||||
|
FbdFeedbackBase *feedback);
|
||||||
|
FbdFeedbackBase *fbd_feedback_profile_get_feedback (FbdFeedbackProfile *self,
|
||||||
|
const char *event_name);
|
||||||
|
FbdFeedbackProfileLevel fbd_feedback_profile_level (const char *name);
|
||||||
|
const char* fbd_feedback_profile_level_to_string (FbdFeedbackProfileLevel level);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-sound"
|
||||||
|
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-feedback-sound.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-sound
|
||||||
|
* @short_description: Describes a feedback via a haptic motor
|
||||||
|
* @Title: FbdFeedbackSound
|
||||||
|
*
|
||||||
|
* The #FbdSoundSound describes the properties of a sound feedback
|
||||||
|
* event. It knows nothing about how to play sound itself but calls
|
||||||
|
* #FbdDevSound for that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_EFFECT,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackSound {
|
||||||
|
FbdFeedbackBase parent;
|
||||||
|
|
||||||
|
gchar *effect;
|
||||||
|
} FbdFeedbackSound;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FbdFeedbackSound, fbd_feedback_sound, FBD_TYPE_FEEDBACK_BASE);
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_effect_finished (FbdFeedbackSound *self)
|
||||||
|
|
||||||
|
{
|
||||||
|
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_run (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (base);
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevSound *sound = fbd_feedback_manager_get_dev_sound (manager);
|
||||||
|
|
||||||
|
g_debug ("Sound event %s", self->effect);
|
||||||
|
fbd_dev_sound_play (sound, self, on_effect_finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_end (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (base);
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevSound *sound = fbd_feedback_manager_get_dev_sound (manager);
|
||||||
|
|
||||||
|
fbd_dev_sound_stop (sound, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EFFECT:
|
||||||
|
self->effect = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackSound *self = FBD_FEEDBACK_SOUND (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_EFFECT:
|
||||||
|
g_value_set_string (value, fbd_feedback_sound_get_effect (self));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_class_init (FbdFeedbackSoundClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_sound_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_sound_get_property;
|
||||||
|
|
||||||
|
base_class->run = fbd_feedback_sound_run;
|
||||||
|
base_class->end = fbd_feedback_sound_end;
|
||||||
|
|
||||||
|
props[PROP_EFFECT] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"effect",
|
||||||
|
"Effect",
|
||||||
|
"The sound effects name",
|
||||||
|
"",
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_sound_init (FbdFeedbackSound *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *fbd_feedback_sound_get_effect (FbdFeedbackSound *self)
|
||||||
|
{
|
||||||
|
return self->effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
fbd_dev_sound_stop(FbdDevSound *self, FbdFeedbackSound *feedback)
|
||||||
|
{
|
||||||
|
/* TODO: can we use cancellable to actually end playback? */
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_SOUND (fbd_feedback_sound_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackSound, fbd_feedback_sound, FBD, FEEDBACK_SOUND, FbdFeedbackBase);
|
||||||
|
|
||||||
|
const char *fbd_feedback_sound_get_effect (FbdFeedbackSound *self);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-theme"
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
#include "fbd-feedback-sound.h"
|
||||||
|
#include "fbd-feedback-theme.h"
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
#include "fbd-feedback-profile.h"
|
||||||
|
|
||||||
|
#include <json-glib/json-glib.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_NAME,
|
||||||
|
PROP_PROFILES,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackTheme {
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
GHashTable *profiles;
|
||||||
|
} FbdFeedbackTheme;
|
||||||
|
|
||||||
|
static void json_serializable_iface_init (JsonSerializableIface *iface);
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (FbdFeedbackTheme, fbd_feedback_theme, G_TYPE_OBJECT,
|
||||||
|
G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
|
||||||
|
json_serializable_iface_init));
|
||||||
|
|
||||||
|
|
||||||
|
static JsonNode *
|
||||||
|
fbd_theme_serializable_serialize_property (JsonSerializable *serializable,
|
||||||
|
const gchar *property_name,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (serializable);
|
||||||
|
JsonNode *node = NULL;
|
||||||
|
|
||||||
|
if (g_strcmp0 (property_name, "profiles") == 0) {
|
||||||
|
GHashTableIter iter;
|
||||||
|
gpointer key;
|
||||||
|
FbdFeedbackProfile *profile;
|
||||||
|
JsonArray *array = json_array_sized_new (FBD_FEEDBACK_PROFILE_N_PROFILES);
|
||||||
|
|
||||||
|
g_hash_table_iter_init (&iter, self->profiles);
|
||||||
|
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *) &profile)) {
|
||||||
|
json_array_add_element (array, json_gobject_serialize (G_OBJECT(profile)));
|
||||||
|
}
|
||||||
|
node = json_node_init_array (json_node_alloc (), array);
|
||||||
|
} else {
|
||||||
|
node = json_serializable_default_serialize_property (serializable,
|
||||||
|
property_name,
|
||||||
|
value,
|
||||||
|
pspec);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fbd_theme_serializable_deserialize_property (JsonSerializable *serializable,
|
||||||
|
const gchar *property_name,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
JsonNode *property_node)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (property_name, "profiles") == 0) {
|
||||||
|
if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) {
|
||||||
|
g_value_set_pointer (value, NULL);
|
||||||
|
return TRUE;
|
||||||
|
} else if (JSON_NODE_TYPE (property_node) == JSON_NODE_ARRAY) {
|
||||||
|
JsonArray *array = json_node_get_array (property_node);
|
||||||
|
guint i, array_len = json_array_get_length (array);
|
||||||
|
GHashTable *profiles = g_hash_table_new_full (g_str_hash,
|
||||||
|
g_str_equal,
|
||||||
|
g_free,
|
||||||
|
(GDestroyNotify)g_object_unref);
|
||||||
|
|
||||||
|
for (i = 0; i < array_len; i++) {
|
||||||
|
JsonNode *element_node = json_array_get_element (array, i);
|
||||||
|
FbdFeedbackProfile *profile;
|
||||||
|
gchar *name;
|
||||||
|
|
||||||
|
if (JSON_NODE_HOLDS_OBJECT (element_node)) {
|
||||||
|
profile = FBD_FEEDBACK_PROFILE (json_gobject_deserialize (FBD_TYPE_FEEDBACK_PROFILE,
|
||||||
|
element_node));
|
||||||
|
name = g_strdup (fbd_feedback_profile_get_name (profile));
|
||||||
|
g_hash_table_insert (profiles, name, profile);
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_value_set_boxed (value, profiles);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
} else {
|
||||||
|
return json_serializable_default_deserialize_property (serializable,
|
||||||
|
property_name,
|
||||||
|
value,
|
||||||
|
pspec,
|
||||||
|
property_node);
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_NAME:
|
||||||
|
g_free (self->name);
|
||||||
|
self->name = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
case PROP_PROFILES:
|
||||||
|
if (self->profiles)
|
||||||
|
g_hash_table_unref (self->profiles);
|
||||||
|
self->profiles = g_value_get_boxed (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_NAME:
|
||||||
|
g_value_set_string (value, self->name);
|
||||||
|
break;
|
||||||
|
case PROP_PROFILES:
|
||||||
|
g_value_set_boxed (value, self->profiles);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->profiles, g_hash_table_unref);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->name, g_free);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
json_serializable_iface_init (JsonSerializableIface *iface)
|
||||||
|
{
|
||||||
|
iface->serialize_property = fbd_theme_serializable_serialize_property;
|
||||||
|
iface->deserialize_property = fbd_theme_serializable_deserialize_property;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_constructed (GObject *object)
|
||||||
|
{
|
||||||
|
FbdFeedbackTheme *self = FBD_FEEDBACK_THEME (object);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (fbd_feedback_theme_parent_class)->constructed (object);
|
||||||
|
|
||||||
|
self->profiles = g_hash_table_new_full (g_str_hash,
|
||||||
|
g_str_equal,
|
||||||
|
g_free,
|
||||||
|
(GDestroyNotify)g_object_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_class_init (FbdFeedbackThemeClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_theme_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_theme_get_property;
|
||||||
|
|
||||||
|
object_class->constructed = fbd_feedback_theme_constructed;
|
||||||
|
object_class->dispose = fbd_feedback_theme_dispose;
|
||||||
|
object_class->finalize = fbd_feedback_theme_finalize;
|
||||||
|
|
||||||
|
props[PROP_NAME] =
|
||||||
|
g_param_spec_string (
|
||||||
|
"name",
|
||||||
|
"Name",
|
||||||
|
"The theme name",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_PROFILES] =
|
||||||
|
g_param_spec_boxed (
|
||||||
|
"profiles",
|
||||||
|
"Profiles",
|
||||||
|
"The feedback profiles",
|
||||||
|
G_TYPE_HASH_TABLE,
|
||||||
|
/* Can't be CONSTRUCT_ONLY since json-glib can't handle it */
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_theme_init (FbdFeedbackTheme *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackTheme *
|
||||||
|
fbd_feedback_theme_new (const gchar *name)
|
||||||
|
{
|
||||||
|
return FBD_FEEDBACK_THEME (g_object_new (FBD_TYPE_FEEDBACK_THEME,
|
||||||
|
"name", name,
|
||||||
|
NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FbdFeedbackTheme *
|
||||||
|
fbd_feedback_theme_new_from_data (const gchar *data, GError **error)
|
||||||
|
{
|
||||||
|
JsonNode *node = json_from_string(data, error);
|
||||||
|
if (!node)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return FBD_FEEDBACK_THEME (json_gobject_deserialize (FBD_TYPE_FEEDBACK_THEME, node));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FbdFeedbackTheme *
|
||||||
|
fbd_feedback_theme_new_from_file (const gchar *filename, GError **error)
|
||||||
|
{
|
||||||
|
g_autofree char *data = NULL;
|
||||||
|
|
||||||
|
if (!g_file_get_contents (filename, &data, NULL, error))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return fbd_feedback_theme_new_from_data (data, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
fbd_feedback_theme_get_name (FbdFeedbackTheme *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_THEME (self), "");
|
||||||
|
|
||||||
|
return self->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fbd_feedback_theme_add_profile (FbdFeedbackTheme *self, FbdFeedbackProfile *profile)
|
||||||
|
{
|
||||||
|
gchar *name;
|
||||||
|
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_THEME (self));
|
||||||
|
g_return_if_fail (FBD_IS_FEEDBACK_PROFILE (profile));
|
||||||
|
name = g_strdup (fbd_feedback_profile_get_name (profile));
|
||||||
|
|
||||||
|
g_hash_table_insert (self->profiles, name, g_object_ref (profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
FbdFeedbackProfile *
|
||||||
|
fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_THEME (self), NULL);
|
||||||
|
|
||||||
|
return g_hash_table_lookup (self->profiles, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
GSList *
|
||||||
|
fbd_feedback_theme_lookup_feedback (FbdFeedbackTheme *self,
|
||||||
|
FbdFeedbackProfileLevel level,
|
||||||
|
FbdEvent *event)
|
||||||
|
{
|
||||||
|
GSList *feedbacks = NULL;
|
||||||
|
|
||||||
|
for (int i = level; i >= FBD_FEEDBACK_PROFILE_LEVEL_SILENT; i--) {
|
||||||
|
const char *profile_name = fbd_feedback_profile_level_to_string (i);
|
||||||
|
FbdFeedbackProfile *profile = fbd_feedback_theme_get_profile (self, profile_name);
|
||||||
|
FbdFeedbackBase *feedback = fbd_feedback_profile_get_feedback (profile,
|
||||||
|
fbd_event_get_event (event));
|
||||||
|
if (feedback) {
|
||||||
|
feedbacks = g_slist_prepend (feedbacks, g_object_ref(feedback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_slist_length (feedbacks))
|
||||||
|
g_warning ("No feedback for event %s", fbd_event_get_event (event));
|
||||||
|
return feedbacks;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-event.h"
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
#include "fbd-feedback-profile.h"
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_THEME (fbd_feedback_theme_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackTheme, fbd_feedback_theme, FBD, FEEDBACK_THEME, GObject);
|
||||||
|
|
||||||
|
FbdFeedbackTheme *fbd_feedback_theme_new (const char *name);
|
||||||
|
FbdFeedbackTheme *fbd_feedback_theme_new_from_data (const gchar *data, GError **error);
|
||||||
|
FbdFeedbackTheme *fbd_feedback_theme_new_from_file (const gchar *filename, GError **error);
|
||||||
|
|
||||||
|
const char *fbd_feedback_theme_get_name (FbdFeedbackTheme *self);
|
||||||
|
void fbd_feedback_theme_add_profile (FbdFeedbackTheme *self,
|
||||||
|
FbdFeedbackProfile *profile);
|
||||||
|
FbdFeedbackProfile *fbd_feedback_theme_get_profile (FbdFeedbackTheme *self, const char *name);
|
||||||
|
|
||||||
|
GSList *fbd_feedback_theme_lookup_feedback (FbdFeedbackTheme *self,
|
||||||
|
FbdFeedbackProfileLevel profile,
|
||||||
|
FbdEvent *event);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-vibra-periodic"
|
||||||
|
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-feedback-vibra-periodic.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-vibra
|
||||||
|
* @short_description: Describes a feedback via a haptic motor
|
||||||
|
* @Title: FbdFeedbackVibra
|
||||||
|
*
|
||||||
|
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||||
|
* event. It knows nothing about the hardware itself but calls
|
||||||
|
* #FbdDevVibra for that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_MAGNITUDE,
|
||||||
|
PROP_FADE_IN_LEVEL,
|
||||||
|
PROP_FADE_IN_TIME,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackVibraPeriodic {
|
||||||
|
FbdFeedbackVibra parent;
|
||||||
|
|
||||||
|
guint magnitude;
|
||||||
|
guint fade_in_level;
|
||||||
|
guint fade_in_time;
|
||||||
|
} FbdFeedbackVibraPeriodic;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FbdFeedbackVibraPeriodic, fbd_feedback_vibra_periodic, FBD_TYPE_FEEDBACK_VIBRA);
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_MAGNITUDE:
|
||||||
|
self->magnitude = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_FADE_IN_LEVEL:
|
||||||
|
self->fade_in_level = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_FADE_IN_TIME:
|
||||||
|
self->fade_in_time = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_MAGNITUDE:
|
||||||
|
g_value_set_uint (value, self->magnitude);
|
||||||
|
break;
|
||||||
|
case PROP_FADE_IN_LEVEL:
|
||||||
|
g_value_set_uint (value, self->fade_in_level);
|
||||||
|
break;
|
||||||
|
case PROP_FADE_IN_TIME:
|
||||||
|
g_value_set_uint (value, self->fade_in_time);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_end_vibra (FbdFeedbackVibra *vibra)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
|
||||||
|
fbd_dev_vibra_stop (dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_start_vibra (FbdFeedbackVibra *vibra)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraPeriodic *self = FBD_FEEDBACK_VIBRA_PERIODIC (vibra);
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
guint duration = fbd_feedback_vibra_get_duration (vibra);
|
||||||
|
|
||||||
|
g_debug ("Periodic Vibra: %d %d %d %d",
|
||||||
|
duration, self->magnitude, self->fade_in_level, self->fade_in_time);
|
||||||
|
|
||||||
|
fbd_dev_vibra_periodic (dev, duration, self->magnitude, self->fade_in_level,
|
||||||
|
self->fade_in_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_class_init (FbdFeedbackVibraPeriodicClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
FbdFeedbackVibraClass *vibra_class = FBD_FEEDBACK_VIBRA_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_vibra_periodic_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_vibra_periodic_get_property;
|
||||||
|
|
||||||
|
vibra_class->start_vibra = fbd_feedback_vibra_periodic_start_vibra;
|
||||||
|
vibra_class->end_vibra = fbd_feedback_vibra_periodic_end_vibra;
|
||||||
|
|
||||||
|
props[PROP_MAGNITUDE] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"magnitude",
|
||||||
|
"Magnitude",
|
||||||
|
"total magnitude",
|
||||||
|
0, 0xFFFF, 0x7FFF,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_FADE_IN_TIME] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"fade-in-time",
|
||||||
|
"Fade in time",
|
||||||
|
"Fade in time until full strength",
|
||||||
|
0, G_MAXINT, 0,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_FADE_IN_LEVEL] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"fade-in-level",
|
||||||
|
"Fade in level",
|
||||||
|
"Fade in start level",
|
||||||
|
0, 0xFFFF, 0x7FFF,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_periodic_init (FbdFeedbackVibraPeriodic *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_VIBRA_PERIODIC (fbd_feedback_vibra_periodic_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackVibraPeriodic, fbd_feedback_vibra_periodic, FBD,
|
||||||
|
FEEDBACK_VIBRA_PERIODIC,
|
||||||
|
FbdFeedbackVibra);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-vibra-rumble"
|
||||||
|
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-feedback-vibra-rumble.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-vibra
|
||||||
|
* @short_description: Describes a feedback via a haptic motor
|
||||||
|
* @Title: FbdFeedbackVibra
|
||||||
|
*
|
||||||
|
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||||
|
* event. It knows nothing about the hardware itself but calls
|
||||||
|
* #FbdDevVibra for that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_COUNT,
|
||||||
|
PROP_PAUSE,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackVibraRumble {
|
||||||
|
FbdFeedbackVibra parent;
|
||||||
|
|
||||||
|
guint count; /* number of rumbles */
|
||||||
|
guint pause; /* pause in msecs */
|
||||||
|
|
||||||
|
guint rumble; /* rumble in msecs */
|
||||||
|
guint periods; /* number of periods to play */
|
||||||
|
guint timer_id;
|
||||||
|
} FbdFeedbackVibraRumble;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (FbdFeedbackVibraRumble, fbd_feedback_vibra_rumble, FBD_TYPE_FEEDBACK_VIBRA);
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_COUNT:
|
||||||
|
self->count = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_PAUSE:
|
||||||
|
self->pause = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (object);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_COUNT:
|
||||||
|
g_value_set_uint (value, self->count);
|
||||||
|
break;
|
||||||
|
case PROP_PAUSE:
|
||||||
|
g_value_set_uint (value, self->pause);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_period_ended (FbdFeedbackVibraRumble *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_VIBRA_RUMBLE (self), G_SOURCE_REMOVE);
|
||||||
|
|
||||||
|
if (self->periods) {
|
||||||
|
fbd_dev_vibra_rumble (dev, self->rumble, FALSE);
|
||||||
|
self->periods--;
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_end_vibra (FbdFeedbackVibra *vibra)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (vibra);
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
|
||||||
|
fbd_dev_vibra_stop (dev);
|
||||||
|
g_clear_handle_id(&self->timer_id, g_source_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_start_vibra (FbdFeedbackVibra *vibra)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraRumble *self = FBD_FEEDBACK_VIBRA_RUMBLE (vibra);
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
guint duration = fbd_feedback_vibra_get_duration (vibra);
|
||||||
|
guint period;
|
||||||
|
|
||||||
|
self->rumble = (duration / self->count) - self->pause;
|
||||||
|
if (self->rumble <= 0) {
|
||||||
|
self->rumble = FBD_FEEDBACK_VIBRA_DEFAULT_DURATION;
|
||||||
|
self->pause = 0;
|
||||||
|
self->count = 1;
|
||||||
|
}
|
||||||
|
period = self->rumble + self->pause;
|
||||||
|
self->periods = self->count;
|
||||||
|
|
||||||
|
g_debug ("Rumble Vibra event: duration %d, rumble: %d, pause: %d, period: %d",
|
||||||
|
duration, self->rumble, self->pause, period);
|
||||||
|
fbd_dev_vibra_rumble (dev, self->rumble, TRUE);
|
||||||
|
self->periods--;
|
||||||
|
if (self->periods) {
|
||||||
|
self->timer_id = g_timeout_add (period, (GSourceFunc) on_period_ended, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_class_init (FbdFeedbackVibraRumbleClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
FbdFeedbackVibraClass *vibra_class = FBD_FEEDBACK_VIBRA_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_vibra_rumble_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_vibra_rumble_get_property;
|
||||||
|
|
||||||
|
vibra_class->start_vibra = fbd_feedback_vibra_rumble_start_vibra;
|
||||||
|
vibra_class->end_vibra = fbd_feedback_vibra_rumble_end_vibra;
|
||||||
|
|
||||||
|
props[PROP_COUNT] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"count",
|
||||||
|
"Count",
|
||||||
|
"The number of rumbles",
|
||||||
|
0, G_MAXINT, 1,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
props[PROP_PAUSE] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"pause",
|
||||||
|
"Pause",
|
||||||
|
"The pause in msecs between rumbles",
|
||||||
|
0, G_MAXINT, 0,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_rumble_init (FbdFeedbackVibraRumble *self)
|
||||||
|
{
|
||||||
|
self->count = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_VIBRA_RUMBLE (fbd_feedback_vibra_rumble_get_type())
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (FbdFeedbackVibraRumble, fbd_feedback_vibra_rumble, FBD,
|
||||||
|
FEEDBACK_VIBRA_RUMBLE,
|
||||||
|
FbdFeedbackVibra);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd-feedback-vibra"
|
||||||
|
|
||||||
|
#include "fbd-enums.h"
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:fbd-feedback-vibra
|
||||||
|
* @short_description: Describes a feedback via a haptic motor
|
||||||
|
* @Title: FbdFeedbackVibra
|
||||||
|
*
|
||||||
|
* The #FbdVibraVibra describes the properties of a haptic feedback
|
||||||
|
* event. It knows nothing about the hardware itself but calls
|
||||||
|
* #FbdDevVibra for that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_DURATION,
|
||||||
|
PROP_LAST_PROP,
|
||||||
|
};
|
||||||
|
static GParamSpec *props[PROP_LAST_PROP];
|
||||||
|
|
||||||
|
typedef struct _FbdFeedbackVibraPrivate {
|
||||||
|
guint duration;
|
||||||
|
guint timer_id;
|
||||||
|
} FbdFeedbackVibraPrivate;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_PRIVATE (FbdFeedbackVibra, fbd_feedback_vibra, FBD_TYPE_FEEDBACK_BASE);
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_timeout_expired (FbdFeedbackVibra *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
FbdDevVibra *dev = fbd_feedback_manager_get_dev_vibra (manager);
|
||||||
|
|
||||||
|
fbd_dev_vibra_remove_effect (dev);
|
||||||
|
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_run (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (base);
|
||||||
|
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||||
|
FbdFeedbackVibraClass *klass;
|
||||||
|
|
||||||
|
klass = FBD_FEEDBACK_VIBRA_GET_CLASS (self);
|
||||||
|
g_return_if_fail (klass->start_vibra);
|
||||||
|
klass->start_vibra (self);
|
||||||
|
|
||||||
|
priv->timer_id = g_timeout_add (priv->duration,
|
||||||
|
(GSourceFunc)on_timeout_expired,
|
||||||
|
self);
|
||||||
|
g_source_set_name_by_id (priv->timer_id, "feedback-vibra-timer");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_end (FbdFeedbackBase *base)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (base);
|
||||||
|
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||||
|
FbdFeedbackVibraClass *klass = FBD_FEEDBACK_VIBRA_GET_CLASS (self);
|
||||||
|
|
||||||
|
if (!priv->timer_id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_return_if_fail (klass->end_vibra);
|
||||||
|
klass->end_vibra(self);
|
||||||
|
g_clear_handle_id(&priv->timer_id, g_source_remove);
|
||||||
|
fbd_feedback_base_done (FBD_FEEDBACK_BASE(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (object);
|
||||||
|
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_DURATION:
|
||||||
|
priv->duration = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibra *self = FBD_FEEDBACK_VIBRA (object);
|
||||||
|
FbdFeedbackVibraPrivate *priv = fbd_feedback_vibra_get_instance_private (self);
|
||||||
|
|
||||||
|
switch (property_id) {
|
||||||
|
case PROP_DURATION:
|
||||||
|
g_value_set_uint (value, priv->duration);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_class_init (FbdFeedbackVibraClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
FbdFeedbackBaseClass *base_class = FBD_FEEDBACK_BASE_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->set_property = fbd_feedback_vibra_set_property;
|
||||||
|
object_class->get_property = fbd_feedback_vibra_get_property;
|
||||||
|
|
||||||
|
base_class->run = fbd_feedback_vibra_run;
|
||||||
|
base_class->end = fbd_feedback_vibra_end;
|
||||||
|
|
||||||
|
props[PROP_DURATION] =
|
||||||
|
g_param_spec_uint (
|
||||||
|
"duration",
|
||||||
|
"Duration",
|
||||||
|
"Vibra event duration in msecs",
|
||||||
|
0, G_MAXUINT, FBD_FEEDBACK_VIBRA_DEFAULT_DURATION,
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fbd_feedback_vibra_init (FbdFeedbackVibra *self)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
guint fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self)
|
||||||
|
{
|
||||||
|
FbdFeedbackVibraPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (FBD_IS_FEEDBACK_VIBRA (self), 0);
|
||||||
|
priv = fbd_feedback_vibra_get_instance_private (self);
|
||||||
|
return priv->duration;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fbd-feedback-base.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define FBD_TYPE_FEEDBACK_VIBRA (fbd_feedback_vibra_get_type())
|
||||||
|
|
||||||
|
#define FBD_FEEDBACK_VIBRA_DEFAULT_DURATION 1000
|
||||||
|
|
||||||
|
G_DECLARE_DERIVABLE_TYPE (FbdFeedbackVibra, fbd_feedback_vibra, FBD, FEEDBACK_VIBRA, FbdFeedbackBase);
|
||||||
|
|
||||||
|
struct _FbdFeedbackVibraClass
|
||||||
|
{
|
||||||
|
FbdFeedbackBaseClass parent_class;
|
||||||
|
|
||||||
|
void (*start_vibra) (FbdFeedbackVibra *self);
|
||||||
|
void (*end_vibra) (FbdFeedbackVibra *self);
|
||||||
|
};
|
||||||
|
|
||||||
|
guint fbd_feedback_vibra_get_duration (FbdFeedbackVibra *self);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define G_LOG_DOMAIN "fbd"
|
||||||
|
|
||||||
|
#include "fbd.h"
|
||||||
|
#include "fbd-feedback-manager.h"
|
||||||
|
#include "lfb-names.h"
|
||||||
|
#include "lfb-gdbus.h"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib-unix.h>
|
||||||
|
|
||||||
|
|
||||||
|
static GMainLoop *loop;
|
||||||
|
|
||||||
|
|
||||||
|
GQuark fbd_error_quark(void)
|
||||||
|
{
|
||||||
|
static GQuark quark = 0;
|
||||||
|
|
||||||
|
if (!quark)
|
||||||
|
quark = g_quark_from_static_string("fbd");
|
||||||
|
|
||||||
|
return quark;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
quit_cb (gpointer user_data)
|
||||||
|
{
|
||||||
|
g_info ("Caught signal, shutting down...");
|
||||||
|
|
||||||
|
if (loop)
|
||||||
|
g_idle_add ((GSourceFunc) g_main_loop_quit, loop);
|
||||||
|
else
|
||||||
|
exit (0);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
bus_acquired_cb (GDBusConnection *connection,
|
||||||
|
const gchar *name,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
FbdFeedbackManager *manager = fbd_feedback_manager_get_default ();
|
||||||
|
|
||||||
|
g_debug ("Bus acquired, creating manager...");
|
||||||
|
|
||||||
|
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (manager),
|
||||||
|
connection,
|
||||||
|
FB_DBUS_PATH,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
name_acquired_cb (GDBusConnection *connection,
|
||||||
|
const gchar *name,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
g_debug ("Service name '%s' was acquired", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
name_lost_cb (GDBusConnection *connection,
|
||||||
|
const gchar *name,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
/* Note that we're not allowing replacement, so once the name acquired, the
|
||||||
|
* process won't lose it. */
|
||||||
|
if (!name) {
|
||||||
|
g_warning ("Could not get the session bus. Make sure "
|
||||||
|
"the message bus daemon is running!");
|
||||||
|
} else {
|
||||||
|
if (connection)
|
||||||
|
g_warning ("Could not acquire the '%s' service name", name);
|
||||||
|
else
|
||||||
|
g_debug ("DBus connection close");
|
||||||
|
}
|
||||||
|
|
||||||
|
g_main_loop_quit (loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
g_autoptr(GOptionContext) opt_context = NULL;
|
||||||
|
|
||||||
|
opt_context = g_option_context_new ("- A daemon to trigger event feedback");
|
||||||
|
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
|
||||||
|
g_warning ("%s", err->message);
|
||||||
|
g_clear_error (&err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_unix_signal_add (SIGTERM, quit_cb, NULL);
|
||||||
|
g_unix_signal_add (SIGINT, quit_cb, NULL);
|
||||||
|
|
||||||
|
loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
|
||||||
|
g_bus_own_name (FB_DBUS_TYPE,
|
||||||
|
FB_DBUS_NAME,
|
||||||
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||||
|
bus_acquired_cb,
|
||||||
|
name_acquired_cb,
|
||||||
|
name_lost_cb,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_main_loop_run (loop);
|
||||||
|
g_main_loop_unref (loop);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FBD_ERROR_FAILED = 0,
|
||||||
|
} FbdError;
|
||||||
|
|
||||||
|
GQuark fbd_error_quark (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Feedback daemon
|
||||||
|
if get_option('daemon')
|
||||||
|
|
||||||
|
fbd_enum_headers = files([
|
||||||
|
'fbd-event.h',
|
||||||
|
'fbd-feedback-vibra.h',
|
||||||
|
])
|
||||||
|
fbd_enum_sources = gnome.mkenums_simple('fbd-enums',
|
||||||
|
sources : fbd_enum_headers)
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
generated_dbus_sources,
|
||||||
|
fbd_enum_sources,
|
||||||
|
'fbd.c',
|
||||||
|
'fbd-dev-vibra.c',
|
||||||
|
'fbd-dev-sound.c',
|
||||||
|
'fbd-event.c',
|
||||||
|
'fbd-feedback-base.c',
|
||||||
|
'fbd-feedback-dummy.c',
|
||||||
|
'fbd-feedback-manager.c',
|
||||||
|
'fbd-feedback-profile.c',
|
||||||
|
'fbd-feedback-sound.c',
|
||||||
|
'fbd-feedback-theme.c',
|
||||||
|
'fbd-feedback-vibra.c',
|
||||||
|
'fbd-feedback-vibra-periodic.c',
|
||||||
|
'fbd-feedback-vibra-rumble.c',
|
||||||
|
]
|
||||||
|
|
||||||
|
fbd_deps = [
|
||||||
|
gio,
|
||||||
|
gio_unix,
|
||||||
|
glib,
|
||||||
|
gobject,
|
||||||
|
gsound,
|
||||||
|
gudev,
|
||||||
|
json_glib,
|
||||||
|
]
|
||||||
|
|
||||||
|
fbd_inc = [
|
||||||
|
include_directories('.'),
|
||||||
|
libfeedback_inc,
|
||||||
|
dbus_inc,
|
||||||
|
]
|
||||||
|
|
||||||
|
fbd_lib = static_library(
|
||||||
|
'fbd',
|
||||||
|
sources,
|
||||||
|
include_directories : fbd_inc,
|
||||||
|
dependencies : fbd_deps,
|
||||||
|
install : false,
|
||||||
|
)
|
||||||
|
|
||||||
|
fbd_dep = declare_dependency(
|
||||||
|
include_directories : fbd_inc,
|
||||||
|
link_with : fbd_lib,
|
||||||
|
dependencies : fbd_deps,
|
||||||
|
)
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'feedbackd',
|
||||||
|
sources : 'fbd.c',
|
||||||
|
include_directories : fbd_inc,
|
||||||
|
dependencies : fbd_dep,
|
||||||
|
install : true,
|
||||||
|
install_dir: libexecdir,
|
||||||
|
)
|
||||||
|
|
||||||
|
endif
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name" : "default",
|
||||||
|
"profiles" : [
|
||||||
|
{
|
||||||
|
"name" : "full",
|
||||||
|
"feedbacks" : [
|
||||||
|
{
|
||||||
|
"type" : "Dummy",
|
||||||
|
"event-name" : "test-dummy-0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Dummy",
|
||||||
|
"event-name" : "test-dummy-10",
|
||||||
|
"duration" : 10000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "quiet",
|
||||||
|
"feedbacks" : [
|
||||||
|
{
|
||||||
|
"type" : "Dummy",
|
||||||
|
"event-name" : "test-dummy-0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Dummy",
|
||||||
|
"event-name" : "test-dummy-10",
|
||||||
|
"duration" : 10000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "silent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
if get_option('tests')
|
||||||
|
|
||||||
|
test_env = environment()
|
||||||
|
test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
|
||||||
|
test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||||
|
test_env.set('G_DEBUG', 'gc-friendly,fatal-warnings')
|
||||||
|
test_env.set('GSETTINGS_BACKEND', 'memory')
|
||||||
|
test_env.set('GSETTINGS_SCHEMA_DIR', '@0@/data'.format(meson.build_root()))
|
||||||
|
test_env.set('PYTHONDONTWRITEBYTECODE', 'yes')
|
||||||
|
test_env.set('MALLOC_CHECK_', '2')
|
||||||
|
test_env.set('XDG_CONFIG_HOME', meson.current_source_dir())
|
||||||
|
test_env.set('XDG_CONFIG_DIRS', meson.current_source_dir())
|
||||||
|
|
||||||
|
# Shared lfbrary Unit tests
|
||||||
|
|
||||||
|
test_lfb_cflags = [
|
||||||
|
'-DTEST_APP_ID="org.sigxcpu.feedbackd_test"',
|
||||||
|
'-DLIBFEEDBACK_USE_UNSTABLE_API',
|
||||||
|
'-DTEST_DATA_DIR="@0@"'.format(join_paths(meson.current_source_dir(), 'data')),
|
||||||
|
]
|
||||||
|
|
||||||
|
test_lfb_link_args = [
|
||||||
|
'-fPIC',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_lfb_deps = [
|
||||||
|
libfeedback_dep,
|
||||||
|
glib,
|
||||||
|
gio_unix,
|
||||||
|
]
|
||||||
|
|
||||||
|
lfb_tests = [
|
||||||
|
'lfb-event',
|
||||||
|
]
|
||||||
|
|
||||||
|
if get_option ('daemon')
|
||||||
|
# The integration test needs the daemon
|
||||||
|
lfb_tests += 'lfb-integration'
|
||||||
|
subdir('services')
|
||||||
|
endif
|
||||||
|
|
||||||
|
foreach test : lfb_tests
|
||||||
|
t = executable('test-@0@'.format(test),
|
||||||
|
['test-@0@.c'.format(test)],
|
||||||
|
c_args : test_lfb_cflags,
|
||||||
|
pie : true,
|
||||||
|
link_args : test_lfb_link_args,
|
||||||
|
dependencies : test_lfb_deps)
|
||||||
|
test(test, t, env: test_env)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
# Daemon Unit tests
|
||||||
|
if get_option('daemon')
|
||||||
|
|
||||||
|
test_fbd_cflags = [
|
||||||
|
'-DTEST_APP_ID="org.sigxcpu.feedbackd_test"',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_fbd_link_args = [
|
||||||
|
'-fPIC',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_fbd_deps = [
|
||||||
|
fbd_dep,
|
||||||
|
]
|
||||||
|
|
||||||
|
fbd_tests = [
|
||||||
|
'fbd-feedback-profile',
|
||||||
|
'fbd-feedback-theme',
|
||||||
|
]
|
||||||
|
|
||||||
|
foreach test : fbd_tests
|
||||||
|
t = executable('test-@0@'.format(test),
|
||||||
|
['test-@0@.c'.format(test)],
|
||||||
|
c_args : test_fbd_cflags,
|
||||||
|
pie : true,
|
||||||
|
link_args : test_fbd_link_args,
|
||||||
|
include_directories : fbd_inc,
|
||||||
|
dependencies : test_fbd_deps)
|
||||||
|
test(test, t, env : test_env)
|
||||||
|
endforeach
|
||||||
|
|
||||||
|
|
||||||
|
endif # daemon
|
||||||
|
|
||||||
|
endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
service_file = 'org.sigxcpu.Feedback.service'
|
||||||
|
service_data = configuration_data()
|
||||||
|
service_data.set('ABS_BUILDDIR', meson.build_root())
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
input : service_file + '.in',
|
||||||
|
output : service_file,
|
||||||
|
configuration : service_data
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[D-BUS Service]
|
||||||
|
Name=org.sigxcpu.Feedback
|
||||||
|
Exec=@ABS_BUILDDIR@/run
|
||||||
|
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fbd-event.h"
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
|
||||||
|
#define TEST_EVENT "window-close"
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_event (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdEvent) event = NULL;
|
||||||
|
g_autofree gchar *appid = NULL;
|
||||||
|
g_autofree gchar *name = NULL;
|
||||||
|
FbdEventEndReason reason;
|
||||||
|
gint timeout;
|
||||||
|
|
||||||
|
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 2);
|
||||||
|
|
||||||
|
g_assert_true (FBD_IS_EVENT (event));
|
||||||
|
g_object_get (event,
|
||||||
|
"end-reason", &reason,
|
||||||
|
"event", &name,
|
||||||
|
"app-id", &appid,
|
||||||
|
"timeout", &timeout,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_assert_cmpstr (fbd_event_get_event (event), ==, TEST_EVENT);
|
||||||
|
g_assert_cmpstr (name, ==, TEST_EVENT);
|
||||||
|
|
||||||
|
g_assert_cmpstr (fbd_event_get_app_id (event), ==, TEST_APP_ID);
|
||||||
|
g_assert_cmpstr (appid, ==, TEST_APP_ID);
|
||||||
|
|
||||||
|
g_assert_cmpint (fbd_event_get_timeout (event), ==, timeout);
|
||||||
|
g_assert_cmpint (timeout, ==, 2);
|
||||||
|
|
||||||
|
g_assert_cmpint (fbd_event_get_end_reason (event), ==, FBD_EVENT_END_REASON_NORMAL);
|
||||||
|
g_assert_cmpint (reason, ==, FBD_EVENT_END_REASON_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_event_feedback (void)
|
||||||
|
{
|
||||||
|
GSList *feedbacks;
|
||||||
|
g_autoptr(FbdEvent) event = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback1 = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
|
||||||
|
|
||||||
|
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, -1);
|
||||||
|
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
|
||||||
|
feedbacks = fbd_event_get_feedbacks (event);
|
||||||
|
g_assert_cmpint (g_slist_length (feedbacks), ==, 0);
|
||||||
|
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
feedbacks = fbd_event_get_feedbacks (event);
|
||||||
|
g_assert_cmpint (g_slist_length (feedbacks), ==, 1);
|
||||||
|
|
||||||
|
/* Remove non existing feedback */
|
||||||
|
fbd_event_remove_feedback (event, FBD_FEEDBACK_BASE(feedback2));
|
||||||
|
feedbacks = fbd_event_get_feedbacks (event);
|
||||||
|
g_assert_cmpint (g_slist_length (feedbacks), ==, 1);
|
||||||
|
|
||||||
|
fbd_event_remove_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
feedbacks = fbd_event_get_feedbacks (event);
|
||||||
|
g_assert_cmpint (g_slist_length (feedbacks), ==, 0);
|
||||||
|
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback2));
|
||||||
|
feedbacks = fbd_event_get_feedbacks (event);
|
||||||
|
g_assert_cmpint (g_slist_length (feedbacks), ==, 2);
|
||||||
|
|
||||||
|
g_assert_false (fbd_event_get_feedbacks_ended (event));
|
||||||
|
fbd_event_end_feedbacks (event);
|
||||||
|
/* Dummy feedback ends immediately */
|
||||||
|
g_assert_true (fbd_event_get_feedbacks_ended (event));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_feedbacks_ended (FbdEvent *event, gboolean *ended)
|
||||||
|
{
|
||||||
|
FBD_IS_EVENT (event);
|
||||||
|
|
||||||
|
*ended = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_event_feedback_ended (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdEvent) event = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback1 = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
|
||||||
|
gboolean ended = FALSE;
|
||||||
|
|
||||||
|
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_ONESHOT);
|
||||||
|
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback2));
|
||||||
|
|
||||||
|
g_signal_connect (event, "feedbacks-ended",
|
||||||
|
(GCallback)on_feedbacks_ended, &ended);
|
||||||
|
|
||||||
|
fbd_event_end_feedbacks (event);
|
||||||
|
g_assert_true (ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_event_feedback_loop (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdEvent) event = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback1 = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
|
||||||
|
gboolean ended = FALSE;
|
||||||
|
|
||||||
|
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, FBD_EVENT_TIMEOUT_LOOP);
|
||||||
|
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback2));
|
||||||
|
|
||||||
|
g_signal_connect (event, "feedbacks-ended",
|
||||||
|
(GCallback)on_feedbacks_ended, &ended);
|
||||||
|
g_assert_false (ended);
|
||||||
|
|
||||||
|
fbd_event_end_feedbacks (event);
|
||||||
|
g_assert_true (ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_event_feedback_timeout (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdEvent) event = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback1 = NULL;
|
||||||
|
g_autoptr(FbdFeedbackDummy) feedback2 = NULL;
|
||||||
|
gboolean ended = FALSE;
|
||||||
|
|
||||||
|
event = fbd_event_new (1, TEST_APP_ID, TEST_EVENT, 1);
|
||||||
|
feedback1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback1));
|
||||||
|
feedback2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY, NULL);
|
||||||
|
fbd_event_add_feedback (event, FBD_FEEDBACK_BASE(feedback2));
|
||||||
|
|
||||||
|
g_signal_connect (event, "feedbacks-ended",
|
||||||
|
(GCallback)on_feedbacks_ended, &ended);
|
||||||
|
g_assert_false (ended);
|
||||||
|
|
||||||
|
fbd_event_end_feedbacks (event);
|
||||||
|
g_assert_true (ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add_func("/feedbackd/fbd/event/new", test_fbd_event);
|
||||||
|
g_test_add_func("/feedbackd/fbd/event/feedbacks/props", test_fbd_event_feedback);
|
||||||
|
g_test_add_func("/feedbackd/fbd/event/feedbacks/ended", test_fbd_event_feedback_ended);
|
||||||
|
g_test_add_func("/feedbackd/fbd/event/feedbacks/loop", test_fbd_event_feedback_loop);
|
||||||
|
g_test_add_func("/feedbackd/fbd/event/feedbacks/timeout", test_fbd_event_feedback_timeout);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fbd-feedback-profile.h"
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
#include "fbd-feedback-vibra.h"
|
||||||
|
|
||||||
|
#include <json-glib/json-glib.h>
|
||||||
|
|
||||||
|
#define PROFILE_NAME "test"
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_profile_name (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdFeedbackProfile) profile = fbd_feedback_profile_new (PROFILE_NAME);
|
||||||
|
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_PROFILE (profile));
|
||||||
|
|
||||||
|
g_assert_cmpstr (fbd_feedback_profile_get_name (profile), ==, PROFILE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_profile_feedbacks (void)
|
||||||
|
{
|
||||||
|
GHashTable *feedbacks;
|
||||||
|
FbdFeedbackBase *fb;
|
||||||
|
g_autoptr (FbdFeedbackDummy) fb1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event1",
|
||||||
|
NULL);
|
||||||
|
g_autoptr (FbdFeedbackDummy) fb2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event2",
|
||||||
|
NULL);
|
||||||
|
g_autoptr(FbdFeedbackProfile) profile = fbd_feedback_profile_new (PROFILE_NAME);
|
||||||
|
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_PROFILE (profile));
|
||||||
|
g_object_get (profile, "feedbacks", &feedbacks, NULL);
|
||||||
|
g_assert_nonnull (feedbacks);
|
||||||
|
g_hash_table_unref (feedbacks);
|
||||||
|
fbd_feedback_profile_add_feedback (profile, FBD_FEEDBACK_BASE(fb1));
|
||||||
|
fbd_feedback_profile_add_feedback (profile, FBD_FEEDBACK_BASE(fb2));
|
||||||
|
|
||||||
|
fb = fbd_feedback_profile_get_feedback (profile, "event1");
|
||||||
|
g_assert_cmpstr (fbd_feedback_get_event_name (fb), ==, "event1");
|
||||||
|
|
||||||
|
fb = fbd_feedback_profile_get_feedback (profile, "does-not-exist");
|
||||||
|
g_assert_null (fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_profile_parse (void)
|
||||||
|
{
|
||||||
|
const char *json =" "
|
||||||
|
" { "
|
||||||
|
" \"name\" : \"full\", "
|
||||||
|
" \"feedbacks\" : [ "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"vibra\", "
|
||||||
|
" \"event-name\" : \"event1\" "
|
||||||
|
" }, "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"dummy\", "
|
||||||
|
" \"event-name\" : \"event2\" "
|
||||||
|
" } "
|
||||||
|
" ] "
|
||||||
|
" } ";
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
g_autoptr (FbdFeedbackProfile) profile = NULL;
|
||||||
|
JsonNode *node;
|
||||||
|
FbdFeedbackBase *fb;
|
||||||
|
|
||||||
|
node = json_from_string(json, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
profile = FBD_FEEDBACK_PROFILE (json_gobject_deserialize (FBD_TYPE_FEEDBACK_PROFILE, node));
|
||||||
|
g_assert_nonnull (profile);
|
||||||
|
fb = fbd_feedback_profile_get_feedback (profile, "event2");
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_DUMMY(fb));
|
||||||
|
fb = fbd_feedback_profile_get_feedback (profile, "event1");
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_VIBRA(fb));
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-profile/name", test_fbd_feedback_profile_name);
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-profile/feedbacks", test_fbd_feedback_profile_feedbacks);
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-profile/parse", test_fbd_feedback_profile_parse);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fbd-feedback-dummy.h"
|
||||||
|
#include "fbd-feedback-theme.h"
|
||||||
|
|
||||||
|
#include <json-glib/json-glib.h>
|
||||||
|
|
||||||
|
#define THEME_NAME "test"
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_theme_name (void)
|
||||||
|
{
|
||||||
|
g_autoptr(FbdFeedbackTheme) theme = fbd_feedback_theme_new (THEME_NAME);
|
||||||
|
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_THEME (theme));
|
||||||
|
|
||||||
|
g_assert_cmpstr (fbd_feedback_theme_get_name (theme), ==, THEME_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_theme_profiles (void)
|
||||||
|
{
|
||||||
|
g_autoptr (FbdFeedbackDummy) quiet_fb1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event1",
|
||||||
|
NULL);
|
||||||
|
g_autoptr (FbdFeedbackDummy) quiet_fb2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event2",
|
||||||
|
NULL);
|
||||||
|
g_autoptr (FbdFeedbackDummy) full_fb1 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event1",
|
||||||
|
NULL);
|
||||||
|
g_autoptr (FbdFeedbackDummy) full_fb2 = g_object_new (FBD_TYPE_FEEDBACK_DUMMY,
|
||||||
|
"event-name", "event2",
|
||||||
|
NULL);
|
||||||
|
g_autoptr(FbdFeedbackTheme) theme = fbd_feedback_theme_new (THEME_NAME);
|
||||||
|
|
||||||
|
FbdFeedbackProfile *profile_full = fbd_feedback_profile_new ("full");
|
||||||
|
FbdFeedbackProfile *profile_quiet = fbd_feedback_profile_new ("quiet");
|
||||||
|
FbdFeedbackProfile *profile;
|
||||||
|
|
||||||
|
fbd_feedback_profile_add_feedback (profile_quiet, FBD_FEEDBACK_BASE(quiet_fb1));
|
||||||
|
fbd_feedback_profile_add_feedback (profile_quiet, FBD_FEEDBACK_BASE(quiet_fb2));
|
||||||
|
|
||||||
|
fbd_feedback_profile_add_feedback (profile_full, FBD_FEEDBACK_BASE(full_fb1));
|
||||||
|
fbd_feedback_profile_add_feedback (profile_full, FBD_FEEDBACK_BASE(full_fb2));
|
||||||
|
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_THEME (theme));
|
||||||
|
fbd_feedback_theme_add_profile (theme, profile_quiet);
|
||||||
|
fbd_feedback_theme_add_profile (theme, profile_full);
|
||||||
|
|
||||||
|
profile = fbd_feedback_theme_get_profile (theme, "full");
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_PROFILE (profile));
|
||||||
|
|
||||||
|
g_print ("%s\n", json_gobject_to_data (G_OBJECT(theme), NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_fbd_feedback_theme_parse (void)
|
||||||
|
{
|
||||||
|
const char *json =" "
|
||||||
|
"{ "
|
||||||
|
" \"name\" : \"test\", "
|
||||||
|
" \"profiles\" : [ "
|
||||||
|
" { "
|
||||||
|
" \"name\" : \"full\", "
|
||||||
|
" \"feedbacks\" : [ "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"dummy\", "
|
||||||
|
" \"event-name\" : \"event1\" "
|
||||||
|
" }, "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"dummy\", "
|
||||||
|
" \"event-name\" : \"event2\" "
|
||||||
|
" } "
|
||||||
|
" ] "
|
||||||
|
" }, "
|
||||||
|
" { "
|
||||||
|
" \"name\" : \"quiet\", "
|
||||||
|
" \"feedbacks\" : [ "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"dummy\", "
|
||||||
|
" \"event-name\" : \"event1\" "
|
||||||
|
" }, "
|
||||||
|
" { "
|
||||||
|
" \"type\" : \"dummy\", "
|
||||||
|
" \"event-name\" : \"event2\" "
|
||||||
|
" } "
|
||||||
|
" ] "
|
||||||
|
" } "
|
||||||
|
" ] "
|
||||||
|
"} ";
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
g_autoptr (FbdFeedbackTheme) theme = NULL;
|
||||||
|
FbdFeedbackProfile *profile;
|
||||||
|
|
||||||
|
theme = fbd_feedback_theme_new_from_data (json, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_nonnull (theme);
|
||||||
|
profile = fbd_feedback_theme_get_profile (theme, "full");
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile));
|
||||||
|
g_assert_cmpstr ("full", ==,
|
||||||
|
fbd_feedback_profile_get_name (profile));
|
||||||
|
profile = fbd_feedback_theme_get_profile (theme, "quiet");
|
||||||
|
g_assert_true (FBD_IS_FEEDBACK_PROFILE(profile));
|
||||||
|
g_assert_cmpstr ("quiet", ==,
|
||||||
|
fbd_feedback_profile_get_name (profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-theme/name", test_fbd_feedback_theme_name);
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-theme/profiles", test_fbd_feedback_theme_profiles);
|
||||||
|
g_test_add_func("/feedbackd/fbd/feedback-theme/parse", test_fbd_feedback_theme_parse);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libfeedback.h"
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
/* Property set/get */
|
||||||
|
static void
|
||||||
|
test_lfb_event_props (void)
|
||||||
|
{
|
||||||
|
g_autoptr(LfbEvent) event = NULL;
|
||||||
|
g_autofree gchar *evname = NULL;
|
||||||
|
gint timeout;
|
||||||
|
|
||||||
|
g_assert_true (lfb_init (TEST_APP_ID, NULL));
|
||||||
|
|
||||||
|
event = lfb_event_new ("window-close");
|
||||||
|
g_assert_true (LFB_IS_EVENT (event));
|
||||||
|
|
||||||
|
g_object_get (event, "event", &evname, NULL);
|
||||||
|
g_assert_cmpstr (evname, ==, "window-close");
|
||||||
|
|
||||||
|
g_object_get (event, "timeout", &timeout, NULL);
|
||||||
|
g_assert_cmpint (timeout, ==, -1);
|
||||||
|
|
||||||
|
g_assert_cmpint (lfb_event_get_end_reason (event), ==, LFB_EVENT_END_REASON_NATURAL);
|
||||||
|
g_assert_cmpint (lfb_event_get_state (event), ==, LFB_EVENT_STATE_NONE);
|
||||||
|
|
||||||
|
lfb_uninit ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test failure paths when no feedback daemon is running */
|
||||||
|
static void
|
||||||
|
test_lfb_event_trigger (void)
|
||||||
|
{
|
||||||
|
g_autoptr(LfbEvent) event = NULL;
|
||||||
|
g_autofree gchar *evname = NULL;
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
|
||||||
|
g_assert_true (lfb_init (TEST_APP_ID, NULL));
|
||||||
|
|
||||||
|
event = lfb_event_new ("window-close");
|
||||||
|
g_assert_false (lfb_event_trigger_feedback (event, &err));
|
||||||
|
g_assert_nonnull (err);
|
||||||
|
g_clear_error (&err);
|
||||||
|
|
||||||
|
g_assert_false (lfb_event_end_feedback (event, &err));
|
||||||
|
g_assert_nonnull (err);
|
||||||
|
g_clear_error (&err);
|
||||||
|
|
||||||
|
lfb_uninit ();
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_autoptr(GTestDBus) bus = g_test_dbus_new (0);
|
||||||
|
g_test_dbus_up (bus);
|
||||||
|
|
||||||
|
g_test_add_func("/feedbackd/libfeedback/lfb-event/props", test_lfb_event_props);
|
||||||
|
g_test_add_func("/feedbackd/libfeedback/lfb-event/trigger", test_lfb_event_trigger);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Purism SPC
|
||||||
|
* SPDX-License-Identifier: GPL-3.0+
|
||||||
|
* Author: Guido Günther <agx@sigxcpu.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libfeedback.h"
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GTestDBus *dbus;
|
||||||
|
|
||||||
|
} TestFixture;
|
||||||
|
|
||||||
|
GMainLoop *mainloop;
|
||||||
|
|
||||||
|
static void
|
||||||
|
fixture_setup (TestFixture *fixture, gconstpointer unused)
|
||||||
|
{
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
gchar *relative, *servicesdir;
|
||||||
|
gint success;
|
||||||
|
|
||||||
|
fixture->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
|
||||||
|
relative = g_test_build_filename (G_TEST_BUILT, "services", NULL);
|
||||||
|
servicesdir = g_canonicalize_filename (relative, NULL);
|
||||||
|
g_free (relative);
|
||||||
|
|
||||||
|
g_test_dbus_add_service_dir (fixture->dbus, servicesdir);
|
||||||
|
g_free (servicesdir);
|
||||||
|
g_setenv ("FEEDBACK_THEME", TEST_DATA_DIR "/test.json", TRUE);
|
||||||
|
g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);
|
||||||
|
g_test_dbus_up (fixture->dbus);
|
||||||
|
|
||||||
|
g_assert_null (mainloop);
|
||||||
|
mainloop = g_main_loop_new (NULL, FALSE);
|
||||||
|
|
||||||
|
success = lfb_init (TEST_APP_ID, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fixture_teardown (TestFixture *fixture, gconstpointer unused)
|
||||||
|
{
|
||||||
|
g_clear_pointer (&mainloop, g_main_loop_unref);
|
||||||
|
lfb_uninit ();
|
||||||
|
|
||||||
|
g_test_dbus_down (fixture->dbus);
|
||||||
|
g_object_unref (fixture->dbus);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_feedback_ended (LfbEvent *event, LfbEvent **cmp)
|
||||||
|
{
|
||||||
|
g_assert_true (LFB_IS_EVENT (event));
|
||||||
|
g_assert_null (*cmp);
|
||||||
|
|
||||||
|
g_debug ("Feedback ended for %s", lfb_event_get_event (event));
|
||||||
|
g_assert_cmpint (lfb_event_get_end_reason (event), ==, LFB_EVENT_END_REASON_EXPLICIT);
|
||||||
|
|
||||||
|
/* "Return" event */
|
||||||
|
*cmp = event;
|
||||||
|
g_main_loop_quit (mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_lfb_integration_event_sync (void)
|
||||||
|
{
|
||||||
|
g_autoptr(LfbEvent) event0 = NULL;
|
||||||
|
g_autoptr(LfbEvent) event1 = NULL;
|
||||||
|
g_autoptr(LfbEvent) event10 = NULL;
|
||||||
|
g_autofree gchar *evname = NULL;
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
LfbEvent *cmp = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
event0 = lfb_event_new ("test-dummy-0");
|
||||||
|
success = lfb_event_trigger_feedback (event0, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
event10 = lfb_event_new ("test-dummy-10");
|
||||||
|
g_signal_connect (event10, "feedback-ended", (GCallback)on_feedback_ended, &cmp);
|
||||||
|
success = lfb_event_trigger_feedback (event10, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
success = lfb_event_end_feedback (event10, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
g_main_loop_run (mainloop);
|
||||||
|
|
||||||
|
/* If the signal fired cmp will match event */
|
||||||
|
g_assert_true (event10 == cmp);
|
||||||
|
g_assert_cmpint (lfb_event_get_state (event10), ==, LFB_EVENT_STATE_ENDED);
|
||||||
|
g_assert_cmpint (lfb_event_get_end_reason (event10), ==, LFB_EVENT_END_REASON_EXPLICIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_event_triggered (LfbEvent *event,
|
||||||
|
GAsyncResult *res,
|
||||||
|
LfbEvent **cmp)
|
||||||
|
{
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
g_assert_true (LFB_IS_EVENT (event));
|
||||||
|
g_assert_null (*cmp);
|
||||||
|
|
||||||
|
g_debug ("%s", __func__);
|
||||||
|
success = lfb_event_trigger_feedback_finish (event, res, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
/* "Return" event */
|
||||||
|
*cmp = event;
|
||||||
|
g_main_loop_quit (mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_event_ended (LfbEvent *event,
|
||||||
|
GAsyncResult *res,
|
||||||
|
LfbEvent **cmp)
|
||||||
|
{
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
g_debug ("%s", __func__);
|
||||||
|
g_assert_true (LFB_IS_EVENT (event));
|
||||||
|
g_assert_null (*cmp);
|
||||||
|
|
||||||
|
success = lfb_event_end_feedback_finish (event, res, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
/* "Return" event */
|
||||||
|
*cmp = event;
|
||||||
|
g_main_loop_quit (mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_lfb_integration_event_async (void)
|
||||||
|
{
|
||||||
|
g_autoptr(LfbEvent) event0 = NULL;
|
||||||
|
g_autoptr(LfbEvent) event10 = NULL;
|
||||||
|
g_autofree gchar *evname = NULL;
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
LfbEvent *cmp = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
|
event0 = lfb_event_new ("test-dummy-0");
|
||||||
|
lfb_event_trigger_feedback_async (event0,
|
||||||
|
NULL,
|
||||||
|
(GAsyncReadyCallback)on_event_triggered,
|
||||||
|
&cmp);
|
||||||
|
g_main_loop_run (mainloop);
|
||||||
|
/* The async finish callback saw the right event */
|
||||||
|
g_assert_true (event0 == cmp);
|
||||||
|
cmp = NULL;
|
||||||
|
|
||||||
|
event10 = lfb_event_new ("test-dummy-10");
|
||||||
|
success = lfb_event_trigger_feedback (event10, &err);
|
||||||
|
g_assert_no_error (err);
|
||||||
|
g_assert_true (success);
|
||||||
|
|
||||||
|
lfb_event_end_feedback_async (event10,
|
||||||
|
NULL,
|
||||||
|
(GAsyncReadyCallback)on_event_ended,
|
||||||
|
&cmp);
|
||||||
|
|
||||||
|
g_main_loop_run (mainloop);
|
||||||
|
/* The async finish callback saw the right event */
|
||||||
|
g_assert_true (event10 == cmp);
|
||||||
|
g_assert_cmpint (lfb_event_get_state (event10), ==, LFB_EVENT_STATE_ENDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_profile_changed (LfbGdbusFeedback *proxy, GParamSpec *psepc, const gchar **profile)
|
||||||
|
{
|
||||||
|
g_assert_null (*profile);
|
||||||
|
*profile = lfb_get_feedback_profile();
|
||||||
|
g_debug("Set feedback profile to: '%s'", *profile);
|
||||||
|
|
||||||
|
g_main_loop_quit (mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_lfb_integration_profile (void)
|
||||||
|
{
|
||||||
|
g_autoptr (GError) err = NULL;
|
||||||
|
LfbGdbusFeedback *proxy;
|
||||||
|
gchar *cmp = NULL;
|
||||||
|
|
||||||
|
g_assert_cmpstr (lfb_get_feedback_profile (), ==, "full");
|
||||||
|
proxy = lfb_get_proxy ();
|
||||||
|
g_assert_nonnull (proxy);
|
||||||
|
|
||||||
|
lfb_set_feedback_profile ("quiet");
|
||||||
|
g_signal_connect (proxy, "notify::profile", (GCallback)on_profile_changed, &cmp);
|
||||||
|
g_main_loop_run (mainloop);
|
||||||
|
g_assert_cmpstr (lfb_get_feedback_profile (), ==, "quiet");
|
||||||
|
g_assert_cmpstr (cmp, ==, "quiet");
|
||||||
|
}
|
||||||
|
|
||||||
|
gint
|
||||||
|
main (gint argc, gchar *argv[])
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add("/feedbackd/lfb-integration/event_sync", TestFixture, NULL,
|
||||||
|
(gpointer)fixture_setup,
|
||||||
|
(gpointer)test_lfb_integration_event_sync,
|
||||||
|
(gpointer)fixture_teardown);
|
||||||
|
|
||||||
|
g_test_add("/feedbackd/lfb-integration/event_async", TestFixture, NULL,
|
||||||
|
(gpointer)fixture_setup,
|
||||||
|
(gpointer)test_lfb_integration_event_async,
|
||||||
|
(gpointer)fixture_teardown);
|
||||||
|
|
||||||
|
g_test_add("/feedbackd/lfb-integration/profile", TestFixture, NULL,
|
||||||
|
(gpointer)fixture_setup,
|
||||||
|
(gpointer)test_lfb_integration_profile,
|
||||||
|
(gpointer)fixture_teardown);
|
||||||
|
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user