Phase 0: Repo-Gerüst, Dokumentation und Machbarkeits-Spike
Vier Beweise, die das Projekt tragen, als eigenständig lauffähige Skripte:
S1 Plasma Mobile in proot über genestetes kwin_wayland im X11-Backend.
Fällt selbsttätig auf startplasmamobile und dann auf XFCE zurück, damit
ein Fehlschlag eindeutig Plasma zuzuordnen ist und nicht der Umgebung.
S2 Mikrofon über PulseAudio mit OpenSL-Quelle. Misst den Pegel statt nur die
Dateigröße — der typische Fehlschlag ist eine formal korrekte WAV-Datei
voller Stille.
S3 Virtuelle PipeWire-Kamera per GStreamer-pipewiresink, ohne Kernel-Modul.
Setzt die Firefox-Einstellung vorab, damit der Test nicht an einem
vergessenen Häkchen scheitert.
S4 Intent-Handoff mit lauffähiger Mini-Bridge: xdg-open-Handler für geo:,
tel: und sms:, Token-Authentifizierung, feste Liste erlaubter Schemata.
provision/provision.sh setzt die Hintergrundbeschränkungen per ADB und benennt,
was ADB nicht kann und von Hand erledigt werden muss.
Dokumentiert sind auch die sieben verworfenen Wege — Waydroid, AVF, QEMU, SIP,
APK-Daten-Redirect, Fenster-Durchreichung und der Android-Server — mit Belegen,
damit sie nicht erneut durchdacht werden müssen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Spike-Ergebnisse: Rohdaten bleiben lokal, Auswertung kommt nach docs/
|
||||||
|
spike/out/
|
||||||
|
spike/*.log
|
||||||
|
spike/*.wav
|
||||||
|
|
||||||
|
# Baukram
|
||||||
|
daemon/hpos-daemon
|
||||||
|
daemon/dist/
|
||||||
|
android/build/
|
||||||
|
android/.gradle/
|
||||||
|
android/local.properties
|
||||||
|
*.apk
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Niemals ins Repo: Signaturschlüssel, Zugangsdaten, Backup-Ziele
|
||||||
|
*.keystore
|
||||||
|
*.jks
|
||||||
|
keystore.properties
|
||||||
|
secrets.env
|
||||||
|
config.local.*
|
||||||
|
|
||||||
|
# Editor / OS
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Mitarbeiten
|
||||||
|
|
||||||
|
Das Projekt ist noch in **Phase 0** (Machbarkeits-Spike). Bis die vier Beweise S1–S4
|
||||||
|
stehen, kann sich die Architektur noch grundlegend ändern — Beiträge sind trotzdem
|
||||||
|
willkommen, vor allem Geräte-Meldungen.
|
||||||
|
|
||||||
|
## Am hilfreichsten gerade jetzt
|
||||||
|
|
||||||
|
**Geräte-Meldungen.** Lauf die Skripte aus [`spike/`](spike/) auf deinem Gerät und
|
||||||
|
melde das Ergebnis, egal ob es funktioniert hat. Ein sauber dokumentierter Fehlschlag
|
||||||
|
ist genauso wertvoll wie ein Erfolg — beides geht in die Kompatibilitätsliste.
|
||||||
|
|
||||||
|
## Ehernes Gesetz
|
||||||
|
|
||||||
|
Zwei Regeln, die keine Ausnahme kennen. Ein Beitrag, der eine davon bricht, wird
|
||||||
|
abgelehnt, egal wie nützlich er sonst ist:
|
||||||
|
|
||||||
|
1. **Der Bridge-Layer auf der Android-Seite ist zustandslos.** Er speichert keine
|
||||||
|
Nutzdaten. Alles wird ins Linux durchgereicht und nur dort persistiert.
|
||||||
|
2. **Keine Serverabhängigkeit.** Das Telefon funktioniert vollständig ohne fremde
|
||||||
|
Infrastruktur. Ein externes Backup-Ziel ist optional und jederzeit abwählbar.
|
||||||
|
|
||||||
|
Dazu zwei Regeln, die den Alltag betreffen:
|
||||||
|
|
||||||
|
3. **Kein Root, kein entsperrter Bootloader.** Wenn etwas nur mit Root geht, geht es
|
||||||
|
in diesem Projekt nicht.
|
||||||
|
4. **Kein Privilegienkanal im Dauerbetrieb.** ADB ist ausschließlich für die einmalige
|
||||||
|
Ersteinrichtung vorgesehen. Alles Laufende kommt mit gewöhnlichen App-Berechtigungen aus.
|
||||||
|
|
||||||
|
## Bevor du etwas vorschlägst
|
||||||
|
|
||||||
|
Lies [`docs/verworfene-entscheidungen.md`](docs/verworfene-entscheidungen.md). Sieben
|
||||||
|
naheliegende Wege wurden bereits geprüft und begründet verworfen — Waydroid, AVF, SIP,
|
||||||
|
APK-Daten-Redirect und andere. Wenn du einen davon wiederbeleben willst: gern, aber mit
|
||||||
|
einem Argument, das dort noch nicht widerlegt ist.
|
||||||
|
|
||||||
|
## Stil
|
||||||
|
|
||||||
|
- Skripte: `bash`, `set -euo pipefail`, deutschsprachige Ausgaben, jeder Schritt protokolliert
|
||||||
|
- Kommentare erklären das *Warum*, nicht das *Was*
|
||||||
|
- Architekturentscheidungen kommen als ADR nach `docs/`, nicht in die Commit-Nachricht
|
||||||
|
- Keine Zugangsdaten, Tokens oder Zieladressen im Repo — auch nicht in Beispieldateien
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
|
||||||
|
<https://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
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# HPOS — Linux-Smartphone auf Stock-Android, ohne Root
|
||||||
|
|
||||||
|
> Arbeitstitel. Der Projektname steht noch nicht fest.
|
||||||
|
|
||||||
|
Ein beliebiges gebrauchtes Android-Smartphone wird zum Linux-Telefon: Debian 13 mit
|
||||||
|
Plasma Mobile in Termux/proot, mit einem Backup- und Provisioning-System drumherum,
|
||||||
|
das die eigentliche Substanz des Projekts ausmacht.
|
||||||
|
|
||||||
|
**Kein Root. Kein entsperrter Bootloader. Kein Server nötig.**
|
||||||
|
|
||||||
|
## Was es ist
|
||||||
|
|
||||||
|
"Linux auf dem Handy" gibt es schon. Was es nicht gibt, ist der Rest:
|
||||||
|
|
||||||
|
- **Backup mit echten Snapshots** — Einzeldatei-Restore, Web-Manager im Browser,
|
||||||
|
ZIP-Export aufs Telefon, Ziele über SFTP/SMB/FTP/WebDAV
|
||||||
|
- **Provisioning in einem Rutsch** — Telefon auspacken, eine APK, ~20 Minuten, fertiges System
|
||||||
|
- **Klonen aufs neue Telefon per WLAN** — Gerätewechsel als Normalfall, nicht als Notfall
|
||||||
|
- **Bridge zum Android darunter** — SMS, Telefonie, Kamera, Sensoren erscheinen im Linux
|
||||||
|
über Standard-D-Bus-Schnittstellen, sodass unveränderte Plasma-Anwendungen sie nutzen
|
||||||
|
|
||||||
|
Die Android-Installation darunter ist nur noch Treiber-Layer und Prozess-Host.
|
||||||
|
Alle Nutzdaten leben im Linux und damit im Backup.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
**Phase 0 — Machbarkeits-Spike.** Vier Annahmen tragen das Projekt und werden zuerst
|
||||||
|
bewiesen, bevor Infrastruktur entsteht:
|
||||||
|
|
||||||
|
| | Annahme | Wenn es scheitert |
|
||||||
|
|---|---|---|
|
||||||
|
| **S1** | Plasma Mobile läuft in proot (genestetes KWin) | XFCE-Profil wird Hauptweg |
|
||||||
|
| **S2** | Mikrofon-Eingang erreicht die Linux-Seite | Keine Videocalls, keine Sprachaufnahme |
|
||||||
|
| **S3** | Virtuelle PipeWire-Kamera ohne Kernel-Modul | Kamera nur in einer eigenen App |
|
||||||
|
| **S4** | Intent-Handoff Linux → Android-App | Keine Navigation aus dem Linux-Adressbuch |
|
||||||
|
|
||||||
|
Noch nichts davon ist bewiesen. Die Skripte in [`spike/`](spike/) führen die Beweise;
|
||||||
|
Ergebnisse landen in [`docs/spike-protokoll.md`](docs/spike-protokoll.md).
|
||||||
|
|
||||||
|
## Loslegen
|
||||||
|
|
||||||
|
Getestet wird auf einem **OnePlus 7 Pro (Snapdragon 855, Adreno 640) mit Stock OxygenOS 12**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Auf dem PC, Telefon per USB, USB-Debugging an:
|
||||||
|
./provision/provision.sh
|
||||||
|
|
||||||
|
# 2. Auf dem Telefon in Termux:
|
||||||
|
bash spike/00-bootstrap-termux.sh
|
||||||
|
|
||||||
|
# 3. Beweise der Reihe nach:
|
||||||
|
bash spike/01-s1-plasma-mobile.sh
|
||||||
|
bash spike/02-s2-mikrofon.sh
|
||||||
|
bash spike/03-s3-pipewire-kamera.sh
|
||||||
|
bash spike/04-s4-intent-handoff.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Details und Voraussetzungen: [`spike/README.md`](spike/README.md).
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
- [Architektur](docs/architektur.md) — wie die Teile zusammenhängen
|
||||||
|
- [Machbarkeit](docs/machbarkeit.md) — was ohne Root geht und was nicht, mit Belegen
|
||||||
|
- [Verworfene Entscheidungen](docs/verworfene-entscheidungen.md) — sieben Wege, die
|
||||||
|
geprüft und begründet verworfen wurden. **Vor jedem "warum eigentlich nicht …" lesen.**
|
||||||
|
- [Spike-Protokoll](docs/spike-protokoll.md) — Messergebnisse von Phase 0
|
||||||
|
|
||||||
|
## Geräte
|
||||||
|
|
||||||
|
| Gerät | SoC / GPU | Android | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| OnePlus 7 Pro (`guacamole`) | Snapdragon 855 / Adreno 640 | OxygenOS 12 | 🔬 im Test |
|
||||||
|
|
||||||
|
Läuft es bei dir? [Geräte-Meldung eröffnen](.github/ISSUE_TEMPLATE/geraet-melden.md).
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
GPLv3 — siehe [LICENSE](LICENSE). Das Projekt verteilt Termux (GPLv3) mit;
|
||||||
|
die einheitliche Lizenz erfüllt die Quellen-Angebotspflicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# HPOS — a Linux phone on stock Android, no root
|
||||||
|
|
||||||
|
Turns any second-hand Android phone into a Linux phone: Debian 13 with Plasma Mobile
|
||||||
|
inside Termux/proot, wrapped in the backup and provisioning system that is the actual
|
||||||
|
point of the project.
|
||||||
|
|
||||||
|
**No root. No unlocked bootloader. No server required.**
|
||||||
|
|
||||||
|
Currently in **Phase 0** — proving four load-bearing assumptions (S1–S4 above) before
|
||||||
|
any infrastructure gets built. See [`spike/`](spike/) and
|
||||||
|
[`docs/spike-protokoll.md`](docs/spike-protokoll.md).
|
||||||
|
|
||||||
|
Documentation is currently German-first; English will follow once Phase 0 concludes.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Architektur
|
||||||
|
|
||||||
|
## Überblick
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ Telefon (Stock-Android, unrooted) ────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────┐ ┌────────────────────────────────┐ │
|
||||||
|
│ │ HPOS Bridge (APK) │ │ Termux (+:API, :X11, :Boot) │ │
|
||||||
|
│ │ ZUSTANDSLOS │ │ proot → Debian 13 Trixie │ │
|
||||||
|
│ │ · Default-SMS-App │◄──┤ Plasma Mobile (KWin nested) │ │
|
||||||
|
│ │ · Default-Dialer │ws │ PipeWire ← virt. Kamera │ │
|
||||||
|
│ │ · Kamera-Server │ │ plasma-dialer + spacebar │ │
|
||||||
|
│ │ · NFC-Reader, Sensoren│ │ hpos-daemon (Backup/Web-UI) │ │
|
||||||
|
│ │ · Installer + Updater │ │ appmanager (Intent-Handoff) │ │
|
||||||
|
│ └────────────────────────┘ └────────────────────────────────┘ │
|
||||||
|
│ localhost:8474 │
|
||||||
|
│ /sdcard/hpos-backup/ ← restic-Repo, überlebt Termux-Deinstall │
|
||||||
|
└──────────────────────────────┬─────────────────────────────────────┘
|
||||||
|
│ optional
|
||||||
|
┌──────────┴───────────────────────┐
|
||||||
|
│ Backup-Ziel außer Haus │
|
||||||
|
│ SFTP oder rest-server │
|
||||||
|
│ reines restic-Backend, kein Code │
|
||||||
|
└──────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Telefon ist vollständig eigenständig. Das externe Backup-Ziel ist die **einzige**
|
||||||
|
optionale Außenanbindung — kein Dienst, von dem der Betrieb abhängt.
|
||||||
|
|
||||||
|
## Die zwei tragenden Regeln
|
||||||
|
|
||||||
|
### 1. Der Bridge-Layer ist zustandslos
|
||||||
|
|
||||||
|
Die Android-Seite speichert keine Nutzdaten. SMS, Anrufliste, Kontakte, Fotos — alles
|
||||||
|
wird sofort ins Linux durchgereicht und *nur dort* persistiert.
|
||||||
|
|
||||||
|
Der Grund ist nicht Eleganz, sondern Notwendigkeit: App-private Daten auf Android sind
|
||||||
|
ohne Root **nicht sicherbar**. Gäbe es zwei Orte, an denen Daten liegen, wäre das Backup
|
||||||
|
strukturell unvollständig. Es gibt deshalb genau einen Ort.
|
||||||
|
|
||||||
|
Praktische Folge: Die Android-Seite darf jederzeit plattgemacht werden, ohne dass ein
|
||||||
|
Byte Nutzdaten verloren geht. Das ist zugleich der Abschlusstest des ganzen Projekts.
|
||||||
|
|
||||||
|
### 2. Keine Serverabhängigkeit
|
||||||
|
|
||||||
|
Kein Produktbestandteil darf fremde Infrastruktur voraussetzen. Ein Projekt, das ein
|
||||||
|
Rechenzentrum braucht, benutzt außer dem Autor niemand.
|
||||||
|
|
||||||
|
## Warum D-Bus-Shims statt eigener Anwendungen
|
||||||
|
|
||||||
|
Der Linux-Teil der Bridge schreibt **keine** eigenen Anwendungen für Telefonie, SMS,
|
||||||
|
Akku, Standort oder Sensoren. Stattdessen implementiert er die Standard-D-Bus-Schnittstellen,
|
||||||
|
die der Linux-Desktop ohnehin erwartet:
|
||||||
|
|
||||||
|
| Shim | Ersetzt | Damit läuft unverändert |
|
||||||
|
|---|---|---|
|
||||||
|
| ModemManager | echtes Modem | `plasma-dialer`, `spacebar` |
|
||||||
|
| UPower | Akku-Subsystem | Akkuanzeige, Energieverwaltung |
|
||||||
|
| GeoClue | GPS | Karten, Zeitzone, Wetter |
|
||||||
|
| iio-sensor-proxy | Sensoren | Rotation, Helligkeitsautomatik |
|
||||||
|
|
||||||
|
Das spart nicht nur Arbeit — es bedeutet auch, dass jede Verbesserung upstream geschenkt
|
||||||
|
ankommt und dass ein Gerätewechsel nur die Bridge berührt, nie die Anwendungsschicht.
|
||||||
|
|
||||||
|
## Datenfluss: eingehende SMS
|
||||||
|
|
||||||
|
```
|
||||||
|
Mobilfunk → Android-Framework
|
||||||
|
→ Bridge-APK (Default-SMS-Rolle, persistiert NICHTS)
|
||||||
|
→ WebSocket 127.0.0.1:8474
|
||||||
|
→ ModemManager-Shim
|
||||||
|
→ spacebar → Nachricht landet in der Linux-Datenbank
|
||||||
|
→ restic-Backup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Datenfluss: Navigation aus dem Adressbuch
|
||||||
|
|
||||||
|
```
|
||||||
|
KAddressBook → xdg-open "geo:0,0?q=<adresse>"
|
||||||
|
→ hpos-open (Handler)
|
||||||
|
→ Bridge → startet Begleit-App (Overlay)
|
||||||
|
→ am start VIEW google.navigation:q=<adresse>
|
||||||
|
→ Google Maps im Vollbild auf dem Host
|
||||||
|
```
|
||||||
|
|
||||||
|
Bewusst **kein Streaming**: volles GPU, volles GPS, kein Akkuverbrauch fürs Encodieren,
|
||||||
|
und Overlay-Apps funktionieren wie vorgesehen. Zurück ins Linux über den Task-Manager.
|
||||||
|
|
||||||
|
## Speicherorte
|
||||||
|
|
||||||
|
| Was | Wo | Warum |
|
||||||
|
|---|---|---|
|
||||||
|
| Debian-Rootfs, `$HOME`, alle Nutzdaten | `/data/data/com.termux/files/…` | Echtes ext4 mit Rechten und Symlinks. **Kein Image** — ein normaler Verzeichnisbaum |
|
||||||
|
| restic-Repo | `/sdcard/hpos-backup/` | Überlebt eine Deinstallation von Termux, per USB/MTP erreichbar |
|
||||||
|
| Zweitziel (optional) | SFTP / rest-server | Schützt gegen Verlust oder Diebstahl des Geräts |
|
||||||
|
|
||||||
|
⚠️ Das Rootfs liegt in Termux' App-privatem Verzeichnis. **Termux zu deinstallieren
|
||||||
|
löscht alles.** Deshalb liegt das Backup zwingend außerhalb.
|
||||||
|
|
||||||
|
## Bekannte Grenzen
|
||||||
|
|
||||||
|
Vollständig mit Belegen in [machbarkeit.md](machbarkeit.md), in Kürze:
|
||||||
|
|
||||||
|
- **Anruf-Audio** bleibt auf dem Android-Pfad. Bedienung und Daten liegen im Linux,
|
||||||
|
der Ton kommt aus derselben Hörmuschel wie immer.
|
||||||
|
- **Daten von Host-Apps** sind nicht sicherbar. Deshalb gehören dort nur konto-gebundene
|
||||||
|
Apps hin, bei denen "APK zurück plus anmelden" ein vollständiger Restore ist.
|
||||||
|
- **Kontaktloses Bezahlen** bleibt Sache des Host-Android.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Machbarkeit ohne Root
|
||||||
|
|
||||||
|
Was auf ungerootetem Stock-Android geht und was nicht — mit Belegen, damit niemand
|
||||||
|
gegen eine Wand plant.
|
||||||
|
|
||||||
|
Legende: ✅ bewiesen möglich · 🔬 im Spike zu beweisen · ❌ nachweislich unmöglich ·
|
||||||
|
➖ bewusst außerhalb des Projekts
|
||||||
|
|
||||||
|
## Basis
|
||||||
|
|
||||||
|
| Funktion | | Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| `$HOME` im echten Telefonspeicher, kein Image | ✅ | proot-distro legt einen normalen Verzeichnisbaum an |
|
||||||
|
| Debian + Desktop mit GPU-Beschleunigung | 🔬 | Termux-X11 + Turnip/Zink (Adreno) bzw. virgl/ANGLE (Mali) |
|
||||||
|
| Plasma Mobile in proot | 🔬 | **S1** — `kwin_wayland --x11-display` genestet; braucht dort weder DRM noch logind |
|
||||||
|
| Backup, Snapshots, Einzeldatei-Restore, Web-UI | ✅ | restic + rclone + eigener Daemon |
|
||||||
|
| Klonen auf neues Telefon per WLAN | ✅ | mDNS + restic-REST |
|
||||||
|
|
||||||
|
## Telefonie und Nachrichten
|
||||||
|
|
||||||
|
| Funktion | | Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| SMS empfangen und senden | ✅ | Bridge als Default-SMS-App. Sideloaded → Play-Policy greift nicht |
|
||||||
|
| Anrufe steuern: wählen, annehmen, auflegen, DTMF, Anrufliste | ✅ | Bridge als Default-Dialer (`InCallService`) |
|
||||||
|
| **Anruf-Audio in die Linux-Umgebung leiten** | ❌ | `VOICE_CALL` erfordert `CAPTURE_AUDIO_OUTPUT` — System-App-Permission, seit Android 6 gesperrt; der Accessibility-Umweg wurde 2022 geschlossen |
|
||||||
|
|
||||||
|
**Beleg:** [issuetracker 236553834](https://issuetracker.google.com/issues/236553834)
|
||||||
|
|
||||||
|
Konsequenz: Audio bleibt auf dem Android-Pfad, dieselbe Hörmuschel wie immer. Bedienung
|
||||||
|
und alle Daten liegen im Linux. Ein SIP-Aufbau als Umgehung wurde geprüft und verworfen
|
||||||
|
(siehe [verworfene-entscheidungen.md](verworfene-entscheidungen.md)).
|
||||||
|
|
||||||
|
## Medien
|
||||||
|
|
||||||
|
| Funktion | | Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| Mikrofon im Linux | 🔬 | **S2** — PulseAudio mit OpenSL-Source in Termux, `PULSE_SERVER` in den proot |
|
||||||
|
| Kamera in Browser und Element (Videocall) | 🔬 | **S3** — virtuelle PipeWire-Kamera, reiner Userspace |
|
||||||
|
| Kamera als `/dev/videoX` | ❌ | `v4l2loopback` ist ein Kernel-Modul → Root. Kein Userspace-Ersatz |
|
||||||
|
|
||||||
|
**Beleg für den Kamera-Weg:** [PipeWire is the new v4l2loopback](https://www.ideasonboard.com/news/pipewire-is-the-new-v4l2loopback/) ·
|
||||||
|
[PipeWire-Kamera in Firefox](https://jgrulich.cz/2024/01/30/how-to-use-pipewire-camera-in-firefox/)
|
||||||
|
|
||||||
|
Ein GStreamer-`pipewiresink` mit `media.class=Video/Source` erzeugt einen Kamera-Node
|
||||||
|
ohne Kernel-Modul. Firefox ab 122 nimmt ihn über `media.webrtc.camera.allow-pipewire`
|
||||||
|
und `xdg-desktop-portal-kde` an.
|
||||||
|
|
||||||
|
**Mikrofon-Konflikt:** Android vergibt das Mikro exklusiv an die Vordergrund-App.
|
||||||
|
Solange Termux-X11 vorn ist, hat der Linux-Stack es. Bei einem Mobilfunk-Anruf hat die
|
||||||
|
Telefonie Vorrang. Da es keine nativen Nutzer-Apps im Vordergrund gibt, tritt der
|
||||||
|
Konflikt praktisch kaum auf.
|
||||||
|
|
||||||
|
## Android-Apps auf dem Host
|
||||||
|
|
||||||
|
| Funktion | | Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| Host-App aus dem Linux mit Daten aufrufen | 🔬 | **S4** — `xdg-open`-Handler für `geo:`/`tel:`/`sms:` → Bridge → Intent |
|
||||||
|
| Installierte Apps sichern und wiederherstellen | ✅ | `QUERY_ALL_PACKAGES` + `ApplicationInfo.sourceDir`; APKs unter `/data/app/…/base.apk` sind weltlesbar |
|
||||||
|
| Banking- und TAN-Apps | ✅ | Nativ auf dem Host, Play Integrity ist dort intakt |
|
||||||
|
| **App-Daten sichern** | ❌ | Siehe unten — zwei unabhängige K.-o.-Gründe |
|
||||||
|
| Host-App als Fenster im Linux-Desktop | ➖ | Technisch möglich, bewusst verworfen |
|
||||||
|
|
||||||
|
### Warum App-Daten nicht sicherbar sind
|
||||||
|
|
||||||
|
Zwei Gründe, jeder für sich schon ausreichend:
|
||||||
|
|
||||||
|
1. **Es gibt kein gemeinsames Verzeichnis.** Seit Android 11 ist `/sdcard/Android/data`
|
||||||
|
auch für Inhaber von `MANAGE_EXTERNAL_STORAGE` gesperrt; `/data/data/<pkg>` gehört
|
||||||
|
einer fremden UID mit Modus 0700. App und Termux haben keinen Ort, den beide
|
||||||
|
beschreiben können.
|
||||||
|
**Beleg:** [Storage updates in Android 11](https://developer.android.com/about/versions/11/privacy/storage)
|
||||||
|
2. **APK-Patchen skaliert nicht.** Selbst mit einem solchen Verzeichnis müsste man in
|
||||||
|
beliebigen, obfuszierten APKs jeden I/O-Pfad umschreiben — `getFilesDir`,
|
||||||
|
`SharedPreferences`, `SQLiteOpenHelper`, dazu native Bibliotheken. Bei jedem
|
||||||
|
App-Update von vorn.
|
||||||
|
|
||||||
|
**Konsequenz als Nutzungsregel:** Auf dem Host laufen nur konto-gebundene Apps, bei
|
||||||
|
denen "APK zurück plus einmal anmelden" ein vollständiger Restore ist. Alles mit rein
|
||||||
|
lokalen Daten bekommt eine Linux-Anwendung — TOTP etwa über KeePassXC statt einer
|
||||||
|
Authenticator-App.
|
||||||
|
|
||||||
|
## Hardware-Zugriff
|
||||||
|
|
||||||
|
| Funktion | | Weg |
|
||||||
|
|---|---|---|
|
||||||
|
| Sensoren, Taschenlampe, Akku, GPS, Vibration | ✅ | Termux:API + Bridge |
|
||||||
|
| NFC-Tags lesen und schreiben | ✅ | Bridge-APK |
|
||||||
|
| Kontaktloses Bezahlen (HCE) | ➖ | Framework-only und Play-Integrity-gebunden. Bleibt Sache des Host-Android via Google Pay |
|
||||||
|
|
||||||
|
## Hintergrundbetrieb
|
||||||
|
|
||||||
|
Der Punkt, an dem das Konzept in der Praxis scheitern könnte. Zwei Ebenen:
|
||||||
|
|
||||||
|
**Android-Standardebene** — vollständig per ADB abschaltbar und neustartfest:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell dumpsys deviceidle whitelist +com.termux
|
||||||
|
adb shell cmd appops set com.termux RUN_IN_BACKGROUND allow
|
||||||
|
adb shell cmd appops set com.termux RUN_ANY_IN_BACKGROUND allow
|
||||||
|
adb shell am set-standby-bucket com.termux never
|
||||||
|
adb shell settings put global adaptive_battery_management_enabled 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Entwickleroptionen helfen hier **nicht** — sie betreffen nur gecachte Prozesse.
|
||||||
|
|
||||||
|
**Herstellerebene** — nicht per ADB erreichbar, muss manuell erledigt werden. OxygenOS 12
|
||||||
|
basiert auf ColorOS, dessen App-Einfrierung liegt unterhalb der Android-Mechanismen:
|
||||||
|
Akku-Optimierung für Termux auf "Nicht optimieren", "Sleep Standby Optimization" aus,
|
||||||
|
adaptive Akkunutzung aus, App im Task-Manager anpinnen.
|
||||||
|
|
||||||
|
**Nachweis statt Annahme:** Der Stack muss eine Nacht überleben. Scheitert das, ist es
|
||||||
|
ein früher Warnschuss für das Gesamtkonzept, kein Detail.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Anhang: Android-Server mit redroid
|
||||||
|
|
||||||
|
> **Nicht Teil des Produkts.** Verworfen als [V7](verworfene-entscheidungen.md#v7--android-server-im-rechenzentrum-redroid),
|
||||||
|
> weil sein Zweck sich im Lauf der Planung aufgelöst hat. Hier dokumentiert für den Fall,
|
||||||
|
> dass sich in der Praxis doch eine echte Lücke zeigt.
|
||||||
|
|
||||||
|
## Wann das relevant wird
|
||||||
|
|
||||||
|
Nur für Apps, die **beide** Bedingungen erfüllen:
|
||||||
|
|
||||||
|
1. Ihre Daten liegen rein lokal, nicht bei einem Konto — ein Restore per "APK zurück plus
|
||||||
|
anmelden" würde also Daten verlieren.
|
||||||
|
2. Es gibt keine brauchbare Linux-Anwendung als Ersatz.
|
||||||
|
|
||||||
|
Vor dem Aufbau eines Servers erst prüfen, ob Bedingung 2 wirklich zutrifft. Der
|
||||||
|
häufigste Kandidat, Authenticator-Apps, fällt weg: **KeePassXC** kann TOTP, liegt im
|
||||||
|
Linux und ist damit vollständig im Backup.
|
||||||
|
|
||||||
|
## Warum redroid und nicht Waydroid
|
||||||
|
|
||||||
|
redroid ist explizit als *"Android in Cloud"* entwickelt: läuft in Docker, GPU-beschleunigt,
|
||||||
|
remote-fähig, mehrere Instanzen parallel. Waydroid ist für den lokalen Desktop gedacht und
|
||||||
|
kann kein Remote.
|
||||||
|
|
||||||
|
Zitat aus der Projektdokumentation: *"It is more suitable than Waydroid as a cloud phone."*
|
||||||
|
|
||||||
|
**Quelle:** [remote-android/redroid-doc](https://github.com/remote-android/redroid-doc)
|
||||||
|
|
||||||
|
## Aufbau in Kürze
|
||||||
|
|
||||||
|
- **Host:** x86_64 mit KVM, ~4 GB RAM je Instanz. Auf ARM-Hardware laufen Android-Apps
|
||||||
|
nativ ohne Übersetzung und damit schneller — aber dort fehlt die Werkzeugreife.
|
||||||
|
- **ARM-Apps auf x86:** `libndk_translation` ist in den offiziellen redroid-Images
|
||||||
|
enthalten. Kostet Leistung, einzelne Apps vertragen es nicht.
|
||||||
|
- **Play Services:** nachrüstbar, nötig für Push-Nachrichten.
|
||||||
|
- **Streaming aufs Telefon:** scrcpy über `adb connect`, oder Moonlight/Sunshine für
|
||||||
|
geringere Latenz.
|
||||||
|
- **Kamera und Mikrofon durchreichen:** Stream vom Telefon über einen Tunnel zum Server,
|
||||||
|
dort in `v4l2loopback` bzw. ein virtuelles Audio-Device — auf dem Server ist Root
|
||||||
|
verfügbar, dort ist das unproblematisch.
|
||||||
|
- **Backup:** Container-Daten liegen im Server-Dateisystem, restic sichert sie wie jedes
|
||||||
|
andere Verzeichnis.
|
||||||
|
|
||||||
|
## Was es kostet
|
||||||
|
|
||||||
|
Ein Server, ein Tunnel, ein Streaming-Frontend, ein zweiter Zweig im Backup-System —
|
||||||
|
und Mobilfunkvolumen plus Akku im laufenden Betrieb. Dazu die Abhängigkeit von fremder
|
||||||
|
Infrastruktur, die dem Projekt sonst überall erspart bleibt.
|
||||||
|
|
||||||
|
**Play Integrity** schlägt in Containern in der Regel fehl. Banking-Apps gehören deshalb
|
||||||
|
auf den Host, nicht hierher.
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# Spike-Protokoll — Phase 0
|
||||||
|
|
||||||
|
Ergebnisse der vier Machbarkeitsbeweise. **Dies ist das wichtigste Dokument des
|
||||||
|
Projekts**, solange Phase 0 läuft: alle folgenden Entscheidungen bauen darauf auf.
|
||||||
|
|
||||||
|
Ehrlich ausfüllen, auch und gerade bei Fehlschlägen. Ein sauber dokumentierter
|
||||||
|
Fehlschlag ist wertvoller als ein geschöntes Ergebnis.
|
||||||
|
|
||||||
|
## Gerät
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Modell | OnePlus 7 Pro (`guacamole`) |
|
||||||
|
| SoC / GPU | Snapdragon 855 / Adreno 640 |
|
||||||
|
| RAM / Speicher | _(ausfüllen)_ |
|
||||||
|
| ROM | Stock OxygenOS 12 (Android 12) |
|
||||||
|
| Display | 3120 × 1440, virtuelle Auflösung im Test: _(ausfüllen)_ |
|
||||||
|
| Akku | 4000 mAh, Baujahr 2019 — getauscht? _(ausfüllen)_ |
|
||||||
|
| Datum des Tests | _(ausfüllen)_ |
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
| Beweis | Ergebnis | Konsequenz |
|
||||||
|
|---|---|---|
|
||||||
|
| **S1** Plasma Mobile in proot | ⬜ offen | |
|
||||||
|
| **S2** Mikrofon im Linux | ⬜ offen | |
|
||||||
|
| **S3** Virtuelle PipeWire-Kamera | ⬜ offen | |
|
||||||
|
| **S4** Intent-Handoff | ⬜ offen | |
|
||||||
|
| **Übernacht-Test** Hintergrundbetrieb | ⬜ offen | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S1 — Plasma Mobile in proot
|
||||||
|
|
||||||
|
**Beweisziel:** `kwin_wayland --x11-display` genestet in Termux-X11, darauf
|
||||||
|
`plasma-mobile`. Startet, reagiert auf Touch, Maliit-Tastatur erscheint.
|
||||||
|
|
||||||
|
**Wenn es scheitert:** XFCE-Profil wird Hauptweg. Dann braucht die Kamera
|
||||||
|
`xdg-desktop-portal-gtk` statt `-kde`, die Tastatur wird `onboard`, und Telefonie
|
||||||
|
braucht eine eigene Oberfläche statt `plasma-dialer`.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Ergebnis | _(offen)_ |
|
||||||
|
| Startzeit bis Bedienoberfläche | |
|
||||||
|
| Touch funktioniert | |
|
||||||
|
| Maliit-Tastatur erscheint | |
|
||||||
|
| Rotation | |
|
||||||
|
| Fehlermeldungen | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S2 — Mikrofon im Linux
|
||||||
|
|
||||||
|
**Beweisziel:** PulseAudio in Termux mit OpenSL-Source, `PULSE_SERVER` in den proot,
|
||||||
|
`parecord` nimmt hörbar auf.
|
||||||
|
|
||||||
|
**Wenn es scheitert:** keine Videocalls in Element und im Browser, keine Sprachaufnahme,
|
||||||
|
keine Spracheingabe. Videotelefonie nur noch nativ auf dem Host per Intent-Handoff.
|
||||||
|
Deutliche Abwertung des Produkts.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Ergebnis | _(offen)_ |
|
||||||
|
| Verwendetes PulseAudio-Modul | |
|
||||||
|
| Aufnahme hörbar | |
|
||||||
|
| Latenz / Aussetzer | |
|
||||||
|
| Verhalten bei gesperrtem Bildschirm | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S3 — Virtuelle PipeWire-Kamera
|
||||||
|
|
||||||
|
**Beweisziel:** GStreamer-`pipewiresink` mit `media.class=Video/Source` erzeugt einen
|
||||||
|
Kamera-Node; Firefox mit `media.webrtc.camera.allow-pipewire` sieht ihn auf
|
||||||
|
`webcamtests.com` und zeigt Bild.
|
||||||
|
|
||||||
|
**Wenn es scheitert:** keine Videocalls im Linux, Kamera nur über eine eigenständige App.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Ergebnis | _(offen)_ |
|
||||||
|
| Node erscheint in `wpctl status` | |
|
||||||
|
| Firefox sieht die Kamera | |
|
||||||
|
| Bild kommt an | |
|
||||||
|
| Bildrate / Auflösung | |
|
||||||
|
|
||||||
|
⚠️ **Pop-up-Kamera:** Die Frontkamera des OnePlus 7 Pro fährt motorisiert aus. Prüfen,
|
||||||
|
dass der Node nicht spekulativ die Frontkamera öffnet — das ist Mechanik und Verschleiß.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S4 — Intent-Handoff
|
||||||
|
|
||||||
|
**Beweisziel:** Eine Adresse aus dem Linux startet per `geo:`-Intent die Navigation auf
|
||||||
|
dem Host, mit vorher gestarteter Overlay-Begleit-App.
|
||||||
|
|
||||||
|
**Wenn es scheitert:** keine Navigation aus dem Linux-Adressbuch; Host-Apps müssen
|
||||||
|
manuell über den Task-Manager gestartet werden.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Ergebnis | _(offen)_ |
|
||||||
|
| `geo:`-URI startet Maps | |
|
||||||
|
| `google.navigation:` startet Turn-by-Turn direkt | |
|
||||||
|
| Overlay der Begleit-App liegt über Maps | |
|
||||||
|
| `SYSTEM_ALERT_WINDOW` erteilt | |
|
||||||
|
| Rückweg ins Linux über Task-Manager | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Übernacht-Test — Hintergrundbetrieb
|
||||||
|
|
||||||
|
**Beweisziel:** Der komplette Stack überlebt eine Nacht mit gesperrtem Bildschirm.
|
||||||
|
|
||||||
|
**Wenn es scheitert:** früher Warnschuss für das Gesamtkonzept, kein Detail. OxygenOS 12
|
||||||
|
basiert auf ColorOS mit aggressiver App-Einfrierung unterhalb der Android-Mechanismen.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| ADB-Maßnahmen gesetzt (`provision.sh`) | |
|
||||||
|
| Herstellerseitige Handgriffe erledigt | |
|
||||||
|
| Prozesse nach 8 h noch da | |
|
||||||
|
| Akkuverbrauch über Nacht | |
|
||||||
|
| Was wurde beendet | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Leistungsmessungen
|
||||||
|
|
||||||
|
| Messung | Wert |
|
||||||
|
|---|---|
|
||||||
|
| `vkcube` läuft | |
|
||||||
|
| `glmark2` Score | |
|
||||||
|
| GPU-Treiberpfad (Turnip / Zink / virgl / Software) | |
|
||||||
|
| Virtuelle Auflösung | |
|
||||||
|
| Akku: Leerlauf mit laufendem Stack | |
|
||||||
|
| Akku: Desktop aktiv | |
|
||||||
|
| Thermik / Throttling | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fazit und Entscheidungen
|
||||||
|
|
||||||
|
_(Nach Abschluss ausfüllen: welches Desktop-Profil wird gebaut, welche Fallbacks greifen,
|
||||||
|
was ändert sich am Plan.)_
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
# Verworfene Entscheidungen
|
||||||
|
|
||||||
|
Sieben naheliegende Wege wurden geprüft und begründet verworfen. Sie stehen hier, damit
|
||||||
|
niemand — auch nicht der Autor in einem halben Jahr — von vorn anfängt zu überlegen.
|
||||||
|
|
||||||
|
Wer einen davon wiederbeleben will: gern, aber mit einem Argument, das hier noch nicht
|
||||||
|
widerlegt ist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V1 — Waydroid auf dem Gerät
|
||||||
|
|
||||||
|
**Idee:** Android-Apps in einem Container innerhalb der Linux-Umgebung, damit ihre Daten
|
||||||
|
im Linux-Dateisystem liegen und ins Backup wandern.
|
||||||
|
|
||||||
|
**Warum es nicht geht:** Waydroid braucht `binder` und `ashmem` als Kernel-Module sowie
|
||||||
|
Root für die LXC-Container-Steuerung. proot ist reine Userspace-Emulation von `chroot`
|
||||||
|
und hat prinzipiell keinen Kernel-Zugriff.
|
||||||
|
|
||||||
|
**Beleg:** [waydroid#1301](https://github.com/waydroid/waydroid/discussions/1301) ·
|
||||||
|
[ArchWiki: Waydroid](https://wiki.archlinux.org/title/Waydroid)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V2 — Waydroid in einer VM über AVF
|
||||||
|
|
||||||
|
**Idee:** Wenn proot keinen Kernel hat, dann eben eine echte VM. Die Android
|
||||||
|
Virtualization Framework gibt ungerooteten Apps Zugang zu KVM; in einer eigenen VM
|
||||||
|
liefert man den Gast-Kernel selbst, also mit `binderfs` — und bekäme Waydroid,
|
||||||
|
`v4l2loopback`, systemd und Root *in der VM*, bei intaktem Host.
|
||||||
|
|
||||||
|
Der Ansatz funktioniert tatsächlich: `android.permission.MANAGE_VIRTUAL_MACHINE` ist
|
||||||
|
seit Android 15 per `adb shell pm grant` erteilbar, was gut zum Provisioning-Schritt passt.
|
||||||
|
|
||||||
|
**Warum es trotzdem verworfen wurde** — der Preis:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Qualcomm fällt raus | Snapdragon 8 Elite lässt nur signierte VMs zu. Custom-Linux-VMs gibt es nur auf Tensor, Dimensity 9400, Exynos 2500 |
|
||||||
|
| GPU nur auf Pixel 10 | Gfxstream ist per Overlay-Flag Pixel-10-exklusiv, 47 von 142 Vulkan-Extensions, noch instabil. Auf Pixel 8/9 nur Software-Rendering |
|
||||||
|
| Disk-Image | AVF erzwingt ein Image — im Widerspruch zum Ziel "echter Speicher, kein Image" |
|
||||||
|
|
||||||
|
Das Projekt wäre damit auf ein einziges, teures Gerät festgenagelt worden. Nachdem die
|
||||||
|
Android-Apps ohnehin auf den Host gewandert sind (siehe V7), entfiel auch der Zweck.
|
||||||
|
|
||||||
|
**Beleg:** [AOSP: AVF-Anwendungsfälle](https://source.android.com/docs/core/virtualization/usecases) ·
|
||||||
|
[Run-Linux-on-Android-Guide: AVF](https://github.com/lfdevs/run-linux-on-android-guide/blob/main/docs/en-US/avf-linux.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V3 — QEMU ohne Beschleunigung
|
||||||
|
|
||||||
|
**Idee:** Android in QEMU emulieren, wenn schon keine Virtualisierung verfügbar ist.
|
||||||
|
|
||||||
|
**Warum es nicht geht:** TCG-Emulation läuft bei etwa 5–15 % der nativen Geschwindigkeit.
|
||||||
|
Ein Android-Gast bräuchte Minuten zum Booten und liefe mit wenigen Bildern pro Sekunde.
|
||||||
|
`/dev/kvm` steht gewöhnlichen Apps nicht zur Verfügung — der einzige unrooted Zugang zu
|
||||||
|
KVM ist AVF, also V2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V4 — SIP-Telefonie im Linux
|
||||||
|
|
||||||
|
**Idee:** Da Anruf-Audio nicht abgreifbar ist, die Rufnummer vom Telefon lösen —
|
||||||
|
Portierung zu einem VoIP-Provider, Asterisk als Registrar, Linphone im Linux. Dann
|
||||||
|
telefoniert man tatsächlich im Linux statt nur die Oberfläche dort zu haben.
|
||||||
|
|
||||||
|
**Warum es verworfen wurde:** Der einzige echte Zugewinn wäre das Audio im Linux gewesen.
|
||||||
|
Der Preis stand in keinem Verhältnis.
|
||||||
|
|
||||||
|
| Ohne SIP gewonnen | Ohne SIP verloren |
|
||||||
|
|---|---|
|
||||||
|
| Anrufe ohne Datenverbindung, ohne Tunnel, ohne Server | Gesprächsaufzeichnung im Linux |
|
||||||
|
| Keine dauerhafte Registrierung → deutlich weniger Akkuverbrauch | Eigenes Audio-Routing |
|
||||||
|
| Keine Portierung, kein Wochen-Vorlauf, keine laufenden Kosten | "Desktop klingelt mit" |
|
||||||
|
| Notrufe trivial korrekt statt Sonderfall | |
|
||||||
|
| Gesprächsqualität über Mobilfunk statt VoIP über mobile Daten | |
|
||||||
|
| Eine Anruf-UI statt zwei Ebenen | |
|
||||||
|
|
||||||
|
Alles Verlorene ist Komfort, nichts davon strukturell.
|
||||||
|
|
||||||
|
**Nachrüstbar:** ja, rein additiv und serverseitig, keine Architekturkopplung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V5 — APK patchen, damit Daten ins Linux-`$HOME` wandern
|
||||||
|
|
||||||
|
**Idee:** APKs beim Installieren umschreiben, sodass ihr Datenverzeichnis im Linux liegt
|
||||||
|
und mitgesichert wird.
|
||||||
|
|
||||||
|
**Warum es nicht geht** — zwei unabhängige K.-o.-Gründe, siehe
|
||||||
|
[machbarkeit.md](machbarkeit.md#warum-app-daten-nicht-sicherbar-sind):
|
||||||
|
|
||||||
|
1. Es gibt kein Verzeichnis, das App und Termux beide beschreiben können.
|
||||||
|
`/sdcard/Android/data` ist seit Android 11 auch mit `MANAGE_EXTERNAL_STORAGE` gesperrt,
|
||||||
|
`/data/data/<pkg>` gehört einer fremden UID mit Modus 0700.
|
||||||
|
2. Man müsste in beliebigen, obfuszierten APKs jeden I/O-Pfad umschreiben — inklusive
|
||||||
|
nativer Bibliotheken, und bei jedem App-Update erneut.
|
||||||
|
|
||||||
|
**Was von der Idee überlebt hat:** der Installationsstand. Paketliste und APK-Archiv
|
||||||
|
lassen sich sichern und beim Restore nachinstallieren. Für konto-gebundene Apps ist das
|
||||||
|
ein vollständiger Restore.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V6 — Host-Apps als Fenster im Linux-Desktop
|
||||||
|
|
||||||
|
**Idee:** `scrcpy --new-display=<auflösung> --start-app=<paket>` legt ein virtuelles
|
||||||
|
Display an, startet die App dorthin und streamt nur diese. Jede App bekäme ihr eigenes
|
||||||
|
Fenster im Plasma-Desktop, ohne Vollbild-Umschalten.
|
||||||
|
|
||||||
|
**Es funktioniert.** Ohne Root, ab Android 11.
|
||||||
|
**Beleg:** [scrcpy: virtual_display](https://github.com/Genymobile/scrcpy/blob/master/doc/virtual_display.md)
|
||||||
|
|
||||||
|
**Warum es trotzdem verworfen wurde:** Es braucht dauerhaft Shell-Rechte über einen
|
||||||
|
Shizuku-artigen Kanal — und der überlebt ohne Root **keinen Neustart**. Damit wäre ein
|
||||||
|
permanent bröckelnder Privilegienkanal das Fundament eines Alltagsgeräts geworden.
|
||||||
|
|
||||||
|
Der Verzicht hat mehr eingebracht als gekostet: **das gesamte Produkt kommt jetzt ohne
|
||||||
|
Privilegienkanal im Dauerbetrieb aus.** ADB wird nur noch für die einmalige
|
||||||
|
Ersteinrichtung gebraucht. Für Navigation, Teilen und Scannen ist der Intent-Handoff
|
||||||
|
ohnehin die bessere Lösung — volles GPU, volles GPS, und Overlay-Apps funktionieren, was
|
||||||
|
über einem gestreamten Fenster gar nicht ginge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V7 — Android-Server im Rechenzentrum (redroid)
|
||||||
|
|
||||||
|
**Idee:** Android-Apps in einem redroid-Container auf einem eigenen Server, aufs Telefon
|
||||||
|
gestreamt. Container-Daten liegen serverseitig und sind damit vollständig sicherbar.
|
||||||
|
|
||||||
|
**Warum es verworfen wurde:** Der Zweck hat sich im Lauf der Planung selbst aufgelöst.
|
||||||
|
|
||||||
|
| Ursprünglicher Zweck | Wo es tatsächlich gelandet ist |
|
||||||
|
|---|---|
|
||||||
|
| Banking- und TAN-Apps | Host — dort ist Play Integrity intakt, in einem Container scheitern sie ohnehin |
|
||||||
|
| WhatsApp, Teams, Signal | Host bzw. Matrix/Element nativ im Linux |
|
||||||
|
| Navigation | Host per Intent-Handoff |
|
||||||
|
| Apps mit rein lokalen Daten | Letzter verbleibender Fall — und auch der schrumpft: TOTP deckt KeePassXC im Linux besser ab. Übrig bleiben Offline-Notizen und Spielstände |
|
||||||
|
|
||||||
|
Dafür lohnen ein Server, ein Tunnel, ein Streaming-Frontend und ein eigener Zweig im
|
||||||
|
Backup-System nicht.
|
||||||
|
|
||||||
|
**Bleibt dokumentiert:** [redroid-anhang.md](redroid-anhang.md) — falls sich in der
|
||||||
|
Praxis doch eine echte Lücke zeigt, ist es ein Nachtrag, kein Umbau.
|
||||||
|
|
||||||
|
**Daraus wurde eine Regel:** Kein Produktbestandteil darf eine Serverabhängigkeit
|
||||||
|
einführen. Einzige Ausnahme ist das optionale Backup-Ziel außer Haus.
|
||||||
Executable
+155
@@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# HPOS — Ersteinrichtung vom PC aus, per ADB.
|
||||||
|
#
|
||||||
|
# ADB wird ausschließlich hier gebraucht, einmalig. Im Dauerbetrieb kommt das
|
||||||
|
# System mit gewöhnlichen App-Berechtigungen aus — das ist eine harte Regel des
|
||||||
|
# Projekts, siehe CONTRIBUTING.md.
|
||||||
|
#
|
||||||
|
# Voraussetzung: Telefon per USB, Entwickleroptionen und USB-Debugging aktiv,
|
||||||
|
# der Dialog "Diesem Computer vertrauen" bestätigt.
|
||||||
|
#
|
||||||
|
# Aufruf: ./provision/provision.sh [--dry-run]
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
TERMUX=com.termux
|
||||||
|
TROCKEN=0
|
||||||
|
[ "${1:-}" = "--dry-run" ] && TROCKEN=1
|
||||||
|
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_ERR=$'\033[31m'
|
||||||
|
C_HEAD=$'\033[1;36m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
|
||||||
|
else
|
||||||
|
C_OK=""; C_WARN=""; C_ERR=""; C_HEAD=""; C_DIM=""; C_OFF=""
|
||||||
|
fi
|
||||||
|
kopf() { printf '\n%s══ %s ══%s\n' "$C_HEAD" "$*" "$C_OFF"; }
|
||||||
|
ok() { printf '%s ✓%s %s\n' "$C_OK" "$C_OFF" "$*"; }
|
||||||
|
warn() { printf '%s !%s %s\n' "$C_WARN" "$C_OFF" "$*"; }
|
||||||
|
fehl() { printf '%s ✗%s %s\n' "$C_ERR" "$C_OFF" "$*"; }
|
||||||
|
hinweis() { printf '%s %s%s\n' "$C_DIM" "$*" "$C_OFF"; }
|
||||||
|
|
||||||
|
# Führt ein adb-Kommando aus und meldet das Ergebnis, ohne bei Fehlschlag
|
||||||
|
# abzubrechen: Hersteller-ROMs kennen nicht jedes appops-Ziel, und ein fehlendes
|
||||||
|
# ist kein Grund, die restliche Einrichtung liegen zu lassen.
|
||||||
|
adbs() {
|
||||||
|
local beschreibung="$1"; shift
|
||||||
|
if [ "$TROCKEN" = 1 ]; then
|
||||||
|
printf '%s ? %s%s (adb shell %s)\n' "$C_DIM" "$beschreibung" "$C_OFF" "$*"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local aus
|
||||||
|
if aus=$(adb shell "$@" 2>&1); then
|
||||||
|
if printf '%s' "$aus" | grep -qiE 'error|exception|denied|unknown'; then
|
||||||
|
warn "$beschreibung — ROM hat abgelehnt"
|
||||||
|
hinweis "$(printf '%s' "$aus" | head -1)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
ok "$beschreibung"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "$beschreibung — fehlgeschlagen"
|
||||||
|
hinweis "$(printf '%s' "$aus" | head -1)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Gerät ────────────────────────────────────────────────────────────────────
|
||||||
|
kopf "Gerät"
|
||||||
|
command -v adb >/dev/null 2>&1 || { fehl "adb nicht gefunden. Bitte android-tools-adb installieren."; exit 1; }
|
||||||
|
|
||||||
|
ANZAHL=$(adb devices | grep -cw 'device$' || true)
|
||||||
|
if [ "$ANZAHL" -eq 0 ]; then
|
||||||
|
fehl "Kein Gerät verbunden oder nicht autorisiert."
|
||||||
|
hinweis "adb devices prüfen. Steht dort 'unauthorized', den Dialog auf dem Telefon bestätigen."
|
||||||
|
exit 1
|
||||||
|
elif [ "$ANZAHL" -gt 1 ]; then
|
||||||
|
fehl "Mehrere Geräte verbunden — bitte nur eines anschließen."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for p in ro.product.manufacturer ro.product.model ro.product.device \
|
||||||
|
ro.build.version.release ro.board.platform; do
|
||||||
|
printf ' %-28s %s\n' "$p" "$(adb shell getprop "$p" 2>/dev/null | tr -d '\r')"
|
||||||
|
done
|
||||||
|
[ "$TROCKEN" = 1 ] && warn "Trockenlauf — es wird nichts verändert."
|
||||||
|
|
||||||
|
# ── Termux vorhanden? ────────────────────────────────────────────────────────
|
||||||
|
kopf "Termux"
|
||||||
|
if adb shell pm list packages 2>/dev/null | tr -d '\r' | grep -q "^package:$TERMUX$"; then
|
||||||
|
ok "$TERMUX ist installiert"
|
||||||
|
else
|
||||||
|
fehl "$TERMUX ist nicht installiert."
|
||||||
|
hinweis "Termux von F-Droid oder GitHub installieren, dann dieses Skript erneut laufen lassen."
|
||||||
|
hinweis "Nicht aus dem Play Store — die dortige Fassung ist veraltet und unbrauchbar."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Hintergrundbetrieb, Android-Standardebene ────────────────────────────────
|
||||||
|
# Der Punkt, an dem das Konzept in der Praxis scheitern kann. Diese Maßnahmen
|
||||||
|
# überleben Neustarts. Die Entwickleroptionen helfen hier NICHT — sie betreffen
|
||||||
|
# nur gecachte Prozesse, nicht Vordergrunddienste.
|
||||||
|
kopf "Hintergrundbetrieb (Android-Standardebene)"
|
||||||
|
adbs "Aus der Doze-Beschränkung ausgenommen" dumpsys deviceidle whitelist "+$TERMUX"
|
||||||
|
adbs "RUN_IN_BACKGROUND erlaubt" cmd appops set "$TERMUX" RUN_IN_BACKGROUND allow
|
||||||
|
adbs "RUN_ANY_IN_BACKGROUND erlaubt" cmd appops set "$TERMUX" RUN_ANY_IN_BACKGROUND allow
|
||||||
|
adbs "Standby-Bucket auf 'never' gesetzt" am set-standby-bucket "$TERMUX" never
|
||||||
|
adbs "Adaptive Akkunutzung abgeschaltet" settings put global adaptive_battery_management_enabled 0
|
||||||
|
adbs "Akku-Optimierung ignoriert" cmd appops set "$TERMUX" REQUEST_IGNORE_BATTERY_OPTIMIZATIONS allow
|
||||||
|
|
||||||
|
# Die Zusatz-APKs, sofern schon installiert. Fehlen sie, ist das hier kein Problem.
|
||||||
|
for extra in com.termux.api com.termux.x11 com.termux.boot; do
|
||||||
|
if adb shell pm list packages 2>/dev/null | tr -d '\r' | grep -q "^package:$extra$"; then
|
||||||
|
adbs " $extra aus der Doze-Beschränkung" dumpsys deviceidle whitelist "+$extra"
|
||||||
|
adbs " $extra darf im Hintergrund laufen" cmd appops set "$extra" RUN_ANY_IN_BACKGROUND allow
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Berechtigungen ───────────────────────────────────────────────────────────
|
||||||
|
kopf "Berechtigungen"
|
||||||
|
for perm in \
|
||||||
|
android.permission.RECORD_AUDIO \
|
||||||
|
android.permission.CAMERA \
|
||||||
|
android.permission.ACCESS_FINE_LOCATION \
|
||||||
|
android.permission.READ_EXTERNAL_STORAGE \
|
||||||
|
android.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
do
|
||||||
|
adbs "$(basename "${perm//./\/}") für Termux:API" pm grant com.termux.api "$perm" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
hinweis "Fehlschläge sind normal, solange Termux:API noch nicht installiert ist."
|
||||||
|
|
||||||
|
# ── Was ADB nicht kann ───────────────────────────────────────────────────────
|
||||||
|
kopf "Von Hand zu erledigen"
|
||||||
|
HERSTELLER=$(adb shell getprop ro.product.manufacturer 2>/dev/null | tr -d '\r')
|
||||||
|
cat <<TEXT
|
||||||
|
Die folgenden Punkte liegen unterhalb der Android-Mechanismen und sind per
|
||||||
|
ADB nicht zuverlässig erreichbar. Ohne sie beendet das ROM den Stack über Nacht.
|
||||||
|
|
||||||
|
1. Einstellungen → Apps → Termux → Akku → "Nicht optimieren"
|
||||||
|
2. Einstellungen → Akku → "Sleep Standby Optimization" ausschalten
|
||||||
|
3. Einstellungen → Akku → adaptive Akkunutzung ausschalten
|
||||||
|
4. Termux im Task-Manager anpinnen (Schloss-Symbol)
|
||||||
|
5. Für Overlay-Begleit-Apps: "Über anderen Apps anzeigen" erlauben
|
||||||
|
TEXT
|
||||||
|
case "$HERSTELLER" in
|
||||||
|
[Oo]ne[Pp]lus|OPPO|oppo|realme)
|
||||||
|
printf '\n%s Zu deinem Gerät:%s\n' "$C_WARN" "$C_OFF"
|
||||||
|
hinweis "OxygenOS/ColorOS friert Apps besonders aggressiv ein."
|
||||||
|
hinweis "Zusätzlich unter Einstellungen → Akku → Akkuoptimierung die"
|
||||||
|
hinweis "erweiterte bzw. 'intelligente' Optimierung für Termux abschalten."
|
||||||
|
;;
|
||||||
|
[Xx]iaomi|[Rr]edmi|[Pp]oco|samsung|Samsung)
|
||||||
|
printf '\n%s Zu deinem Gerät:%s\n' "$C_WARN" "$C_OFF"
|
||||||
|
hinweis "Dieser Hersteller ist für aggressives Beenden bekannt."
|
||||||
|
hinweis "Zusätzlich Autostart erlauben und die App von der Speicherbereinigung ausnehmen."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
kopf "Fertig"
|
||||||
|
cat <<TEXT
|
||||||
|
Nächster Schritt auf dem Telefon, in Termux:
|
||||||
|
|
||||||
|
pkg install -y git
|
||||||
|
git clone <repo-url> hpos && cd hpos
|
||||||
|
bash spike/00-bootstrap-termux.sh
|
||||||
|
|
||||||
|
Danach die Beweise der Reihe nach — S1 zuerst, weil er der wackligste ist.
|
||||||
|
Ergebnisse gehören nach docs/spike-protokoll.md.
|
||||||
|
TEXT
|
||||||
Executable
+159
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 0 — Grundinstallation in Termux.
|
||||||
|
#
|
||||||
|
# Richtet Termux-X11, den GPU-Pfad und ein Debian-13-Rootfs ein, damit die Beweise
|
||||||
|
# S1 bis S4 danach laufen können. Idempotent: mehrfaches Ausführen ist unschädlich.
|
||||||
|
#
|
||||||
|
# Ausführen IN TERMUX auf dem Telefon: bash spike/00-bootstrap-termux.sh
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/lib.sh"
|
||||||
|
require_termux
|
||||||
|
|
||||||
|
DISTRO=debian
|
||||||
|
BIND_ROOT="$REPO_ROOT"
|
||||||
|
|
||||||
|
log "HPOS Phase 0 — Bootstrap"
|
||||||
|
log "Protokoll: $LOG"
|
||||||
|
geraete_steckbrief
|
||||||
|
|
||||||
|
# ── Repositories ─────────────────────────────────────────────────────────────
|
||||||
|
# x11-repo bringt Termux-X11 und virglrenderer, tur-repo die Mesa-/Turnip-Pakete.
|
||||||
|
section "Paketquellen"
|
||||||
|
try pkg update -y
|
||||||
|
try pkg install -y x11-repo tur-repo
|
||||||
|
try pkg update -y
|
||||||
|
|
||||||
|
# ── Basispakete ──────────────────────────────────────────────────────────────
|
||||||
|
# Einzeln installiert und einzeln protokolliert: Paketnamen wandern in Termux
|
||||||
|
# gelegentlich, und wir wollen wissen, WELCHES Paket fehlt, statt nur dass etwas fehlt.
|
||||||
|
section "Basispakete"
|
||||||
|
BASIS=(termux-x11-nightly pulseaudio proot-distro termux-api git python openssl termux-tools)
|
||||||
|
for p in "${BASIS[@]}"; do
|
||||||
|
if try pkg install -y "$p"; then ok "$p"; else fail "$p — fehlt, betroffene Beweise siehe unten"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── GPU-Pfad ─────────────────────────────────────────────────────────────────
|
||||||
|
# Adreno 6xx/7xx → Turnip greift direkt auf KGSL zu, das ist der schnellste Weg.
|
||||||
|
# Mali und alles andere → virgl/ANGLE. Wir installieren beides und entscheiden
|
||||||
|
# zur Laufzeit; das Ergebnis landet im GPU-Profil.
|
||||||
|
section "GPU-Pakete"
|
||||||
|
GPU_PKGS=(virglrenderer-android vulkan-loader-android vulkan-tools angle-android
|
||||||
|
mesa-vulkan-icd-wrapper mesa-zink virglrenderer-mesa-zink)
|
||||||
|
for p in "${GPU_PKGS[@]}"; do
|
||||||
|
if try pkg install -y "$p"; then ok "$p"; else warn "$p nicht verfügbar — kein Beinbruch"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
section "GPU erkennen"
|
||||||
|
PLATFORM="$(getprop ro.board.platform 2>/dev/null)"
|
||||||
|
HARDWARE="$(getprop ro.hardware 2>/dev/null)"
|
||||||
|
GPU_PROFIL="virgl" # konservative Vorgabe
|
||||||
|
if have vulkaninfo; then
|
||||||
|
vulkaninfo --summary >>"$LOG" 2>&1 || true
|
||||||
|
GPU_NAME="$(vulkaninfo --summary 2>/dev/null | grep -m1 -i 'deviceName' | cut -d= -f2- | xargs)"
|
||||||
|
else
|
||||||
|
GPU_NAME=""
|
||||||
|
fi
|
||||||
|
log " Plattform = ${PLATFORM:-?}"
|
||||||
|
log " Hardware = ${HARDWARE:-?}"
|
||||||
|
log " GPU = ${GPU_NAME:-unbekannt}"
|
||||||
|
|
||||||
|
case "${GPU_NAME}${PLATFORM}" in
|
||||||
|
*[Aa]dreno*|*qcom*|*sm[0-9]*|*msm*)
|
||||||
|
GPU_PROFIL="turnip"
|
||||||
|
ok "Adreno erkannt → Turnip-Profil (direkter KGSL-Zugriff, schnellster Weg)"
|
||||||
|
;;
|
||||||
|
*[Mm]ali*)
|
||||||
|
GPU_PROFIL="virgl"
|
||||||
|
warn "Mali erkannt → virgl/ANGLE-Profil. Spürbar langsamer als Turnip."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
warn "GPU nicht eindeutig erkannt → virgl als Vorgabe."
|
||||||
|
hint "Bitte im Spike-Protokoll vermerken, welcher Pfad tatsächlich getragen hat."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Das Profil ist eine Datei, kein Sonderfall im Code — so bleibt es später
|
||||||
|
# erweiterbar, ohne dass jemand Skripte anfassen muss.
|
||||||
|
mkdir -p "$REPO_ROOT/rootfs/profiles"
|
||||||
|
PROFIL_DATEI="$OUT_DIR/gpu-profil.env"
|
||||||
|
{
|
||||||
|
echo "# Automatisch erkannt von 00-bootstrap-termux.sh"
|
||||||
|
echo "HPOS_GPU_PROFIL=$GPU_PROFIL"
|
||||||
|
echo "HPOS_GPU_NAME=\"${GPU_NAME:-unbekannt}\""
|
||||||
|
echo "HPOS_PLATFORM=\"${PLATFORM:-unbekannt}\""
|
||||||
|
} >"$PROFIL_DATEI"
|
||||||
|
ok "GPU-Profil geschrieben: $PROFIL_DATEI ($GPU_PROFIL)"
|
||||||
|
|
||||||
|
# ── Termux-Eigenschaften ─────────────────────────────────────────────────────
|
||||||
|
# allow-external-apps ist die Voraussetzung dafür, dass die Bridge-APK später
|
||||||
|
# Kommandos in Termux anstoßen darf. Hier gesetzt, damit Phase 2 es vorfindet.
|
||||||
|
section "Termux-Eigenschaften"
|
||||||
|
TP="$HOME/.termux/termux.properties"
|
||||||
|
mkdir -p "$(dirname "$TP")"
|
||||||
|
touch "$TP"
|
||||||
|
if grep -q '^allow-external-apps' "$TP" 2>/dev/null; then
|
||||||
|
ok "allow-external-apps bereits gesetzt"
|
||||||
|
else
|
||||||
|
echo 'allow-external-apps = true' >>"$TP"
|
||||||
|
ok "allow-external-apps = true gesetzt"
|
||||||
|
hint "Wird erst nach 'termux-reload-settings' oder Neustart von Termux wirksam."
|
||||||
|
fi
|
||||||
|
try termux-reload-settings
|
||||||
|
|
||||||
|
section "Speicherzugriff"
|
||||||
|
if [ -d "$HOME/storage" ]; then
|
||||||
|
ok "Speicherzugriff bereits eingerichtet"
|
||||||
|
else
|
||||||
|
warn "termux-setup-storage wird gleich einen Berechtigungsdialog zeigen — bitte erlauben."
|
||||||
|
try termux-setup-storage
|
||||||
|
sleep 2
|
||||||
|
[ -d "$HOME/storage" ] && ok "Speicherzugriff eingerichtet" || fail "Speicherzugriff fehlt — Backup nach /sdcard geht später nicht"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Wachhalten ───────────────────────────────────────────────────────────────
|
||||||
|
# Ohne Wake-Lock schläft der Stack weg, sobald der Bildschirm aus geht.
|
||||||
|
# Die herstellerseitigen Maßnahmen kommen zusätzlich, siehe provision/.
|
||||||
|
section "Wake-Lock"
|
||||||
|
try termux-wake-lock && ok "Wake-Lock aktiv" || warn "Wake-Lock nicht gesetzt"
|
||||||
|
|
||||||
|
# ── Debian-Rootfs ────────────────────────────────────────────────────────────
|
||||||
|
section "Debian-Rootfs"
|
||||||
|
if proot-distro list --installed 2>/dev/null | grep -q "^$DISTRO\b" \
|
||||||
|
|| [ -d "$PREFIX/var/lib/proot-distro/installed-rootfs/$DISTRO" ]; then
|
||||||
|
ok "$DISTRO bereits installiert"
|
||||||
|
else
|
||||||
|
log " Installiere $DISTRO — das dauert einige Minuten."
|
||||||
|
need proot-distro install "$DISTRO"
|
||||||
|
ok "$DISTRO installiert"
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "Debian-Grundausstattung"
|
||||||
|
# Der Bind macht das Repo im Rootfs unter /hpos sichtbar, damit die inneren
|
||||||
|
# Skripte dieselben Dateien benutzen und Ergebnisse am selben Ort landen.
|
||||||
|
proot-distro login "$DISTRO" --bind "$BIND_ROOT:/hpos" -- bash -lc '
|
||||||
|
set -uo pipefail
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates locales dbus-x11 sudo nano less procps \
|
||||||
|
mesa-utils vulkan-tools pulseaudio-utils
|
||||||
|
# Deutsche und englische Locale, sonst meckert die halbe Desktop-Software.
|
||||||
|
sed -i "s/^# *\(de_DE.UTF-8\|en_US.UTF-8\)/\1/" /etc/locale.gen
|
||||||
|
locale-gen >/dev/null 2>&1
|
||||||
|
echo "--- Debian-Version ---"; cat /etc/debian_version
|
||||||
|
' 2>&1 | tee -a "$LOG"
|
||||||
|
|
||||||
|
# ── Abschluss ────────────────────────────────────────────────────────────────
|
||||||
|
section "Bereit"
|
||||||
|
log "GPU-Profil : $GPU_PROFIL"
|
||||||
|
log "Rootfs : $DISTRO"
|
||||||
|
log "Repo im Rootfs sichtbar unter /hpos"
|
||||||
|
log ""
|
||||||
|
log "Nächste Schritte, in dieser Reihenfolge:"
|
||||||
|
log " bash spike/01-s1-plasma-mobile.sh # der wackligste Beweis, deshalb zuerst"
|
||||||
|
log " bash spike/02-s2-mikrofon.sh"
|
||||||
|
log " bash spike/03-s3-pipewire-kamera.sh"
|
||||||
|
log " bash spike/04-s4-intent-handoff.sh"
|
||||||
|
log ""
|
||||||
|
log "Vor dem Übernacht-Test bitte provision/provision.sh vom PC laufen lassen"
|
||||||
|
log "und die herstellerseitigen Handgriffe aus docs/machbarkeit.md erledigen."
|
||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 0 · Beweis S1 — Läuft Plasma Mobile in proot?
|
||||||
|
#
|
||||||
|
# Der wackligste Beweis des ganzen Projekts, deshalb zuerst. Plasma Mobile will
|
||||||
|
# Wayland, und KWin will normalerweise DRM/KMS und logind — beides gibt es ohne Root
|
||||||
|
# nicht. Die Wette: KWins X11-Backend braucht weder DRM noch Seat, wenn es genestet
|
||||||
|
# in Termux-X11 läuft.
|
||||||
|
#
|
||||||
|
# Scheitert der Beweis, wird XFCE der Hauptweg. Das ist kein Weltuntergang, ändert aber
|
||||||
|
# Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche — siehe docs/spike-protokoll.md.
|
||||||
|
#
|
||||||
|
# Ausführen IN TERMUX: bash spike/01-s1-plasma-mobile.sh
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/lib.sh"
|
||||||
|
require_termux
|
||||||
|
|
||||||
|
DISTRO=debian
|
||||||
|
# Volle Panel-Auflösung wäre 3120x1440. Für einen Telefon-Desktop ist das zu viel
|
||||||
|
# Fläche — allein das Zeichnen frisst die Leistung. Überschreibbar per Umgebung.
|
||||||
|
BREITE="${HPOS_BREITE:-1080}"
|
||||||
|
HOEHE="${HPOS_HOEHE:-2340}"
|
||||||
|
|
||||||
|
[ -f "$OUT_DIR/gpu-profil.env" ] && source "$OUT_DIR/gpu-profil.env"
|
||||||
|
GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}"
|
||||||
|
|
||||||
|
log "HPOS Phase 0 · S1 — Plasma Mobile in proot"
|
||||||
|
log "Auflösung: ${BREITE}x${HOEHE} GPU-Profil: $GPU_PROFIL"
|
||||||
|
log "Protokoll: $LOG"
|
||||||
|
|
||||||
|
# ── Alte Reste einsammeln ────────────────────────────────────────────────────
|
||||||
|
section "Aufräumen"
|
||||||
|
pkill -f termux-x11 2>/dev/null && ok "alten X-Server beendet" || hint "kein alter X-Server"
|
||||||
|
pkill -f virgl_test_server 2>/dev/null && ok "alten virgl-Server beendet" || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# ── X-Server ─────────────────────────────────────────────────────────────────
|
||||||
|
section "Termux-X11 starten"
|
||||||
|
if ! have termux-x11; then
|
||||||
|
fail "termux-x11 fehlt. Bitte 00-bootstrap-termux.sh laufen lassen."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
termux-x11 :0 >>"$LOG" 2>&1 &
|
||||||
|
X11_PID=$!
|
||||||
|
sleep 3
|
||||||
|
if kill -0 "$X11_PID" 2>/dev/null; then
|
||||||
|
ok "X-Server läuft (PID $X11_PID)"
|
||||||
|
else
|
||||||
|
fail "X-Server sofort beendet — siehe Protokoll"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Die App muss im Vordergrund sein, sonst sieht man nichts und Android drosselt.
|
||||||
|
log " Hole die Termux:X11-App in den Vordergrund …"
|
||||||
|
try am start --user 0 -n com.termux.x11/com.termux.x11.MainActivity
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# ── GPU-Server ───────────────────────────────────────────────────────────────
|
||||||
|
section "GPU-Pfad vorbereiten ($GPU_PROFIL)"
|
||||||
|
if [ "$GPU_PROFIL" = "virgl" ] && have virgl_test_server_android; then
|
||||||
|
virgl_test_server_android >>"$LOG" 2>&1 &
|
||||||
|
sleep 1
|
||||||
|
ok "virgl-Server gestartet"
|
||||||
|
elif [ "$GPU_PROFIL" = "turnip" ]; then
|
||||||
|
ok "Turnip greift direkt auf KGSL zu — kein Proxy nötig"
|
||||||
|
else
|
||||||
|
warn "Kein GPU-Server aktiv, Software-Rendering wahrscheinlich"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Audio, damit der Desktop nicht schon daran scheitert ─────────────────────
|
||||||
|
section "PulseAudio"
|
||||||
|
pulseaudio --kill 2>/dev/null || true
|
||||||
|
try pulseaudio --start --exit-idle-time=-1 \
|
||||||
|
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \
|
||||||
|
&& ok "PulseAudio läuft" || warn "PulseAudio nicht gestartet — für S1 nicht kritisch"
|
||||||
|
|
||||||
|
# ── Übergabe an Debian ───────────────────────────────────────────────────────
|
||||||
|
section "Plasma Mobile starten"
|
||||||
|
log " Ab hier läuft alles im Debian-Rootfs. Erster Lauf lädt gut 1 GB Pakete."
|
||||||
|
log " Auf dem Telefon in die Termux:X11-App wechseln, um das Ergebnis zu sehen."
|
||||||
|
log ""
|
||||||
|
|
||||||
|
HPOS_GPU_PROFIL="$GPU_PROFIL" HPOS_BREITE="$BREITE" HPOS_HOEHE="$HOEHE" \
|
||||||
|
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||||
|
bash /hpos/spike/inner/s1-plasma.sh 2>&1 | tee -a "$LOG"
|
||||||
|
|
||||||
|
verdict "S1" "Zeigt die Termux:X11-App eine bedienbare Plasma-Mobile-Oberfläche?
|
||||||
|
Prüfe: reagiert Touch, erscheint die Maliit-Tastatur beim Tippen in ein Feld?"
|
||||||
|
|
||||||
|
log "Aufräumen: pkill -f termux-x11 ; pkill -f virgl_test_server"
|
||||||
Executable
+154
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 0 · Beweis S2 — Erreicht das Mikrofon die Linux-Seite?
|
||||||
|
#
|
||||||
|
# Der stillste K.-o.-Punkt des Projekts. Ohne Mikrofon im Linux gibt es keine
|
||||||
|
# Videocalls in Element oder im Browser, keine Sprachaufnahme, keine Spracheingabe.
|
||||||
|
# Die Wette: Termux' PulseAudio kann über OpenSL ES eine Aufnahmequelle anbieten,
|
||||||
|
# die per TCP in den proot durchgereicht wird.
|
||||||
|
#
|
||||||
|
# Ausführen IN TERMUX: bash spike/02-s2-mikrofon.sh
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/lib.sh"
|
||||||
|
require_termux
|
||||||
|
|
||||||
|
DISTRO=debian
|
||||||
|
DAUER="${HPOS_DAUER:-6}"
|
||||||
|
WAV="$OUT_DIR/s2-aufnahme.wav"
|
||||||
|
|
||||||
|
log "HPOS Phase 0 · S2 — Mikrofon im Linux"
|
||||||
|
log "Protokoll: $LOG"
|
||||||
|
|
||||||
|
# ── Berechtigung ─────────────────────────────────────────────────────────────
|
||||||
|
# Ohne erteilte Mikrofonberechtigung liefert die OpenSL-Quelle Stille statt eines
|
||||||
|
# Fehlers — der verwirrendste denkbare Fehlschlag. Deshalb vorher erzwingen.
|
||||||
|
section "Mikrofonberechtigung"
|
||||||
|
if have termux-microphone-record; then
|
||||||
|
log " Kurzer Test über Termux:API, um den Berechtigungsdialog auszulösen …"
|
||||||
|
try termux-microphone-record -d -l 2 -f "$OUT_DIR/s2-vortest.m4a"
|
||||||
|
sleep 3
|
||||||
|
try termux-microphone-record -q
|
||||||
|
if [ -s "$OUT_DIR/s2-vortest.m4a" ]; then
|
||||||
|
ok "Termux:API kann aufnehmen — Berechtigung liegt vor"
|
||||||
|
else
|
||||||
|
fail "Termux:API konnte nicht aufnehmen"
|
||||||
|
hint "Einstellungen → Apps → Termux:API → Berechtigungen → Mikrofon erlauben."
|
||||||
|
hint "Ohne das ist S2 nicht aussagekräftig."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "termux-microphone-record fehlt (Paket termux-api). Weiter ohne Vortest."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── PulseAudio mit Aufnahmequelle ────────────────────────────────────────────
|
||||||
|
section "PulseAudio mit OpenSL-Quelle"
|
||||||
|
pulseaudio --kill 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# module-sles-source ist der Teil, der wackelt: er existiert nicht in jedem
|
||||||
|
# Termux-Build. Deshalb erst mit, dann ohne — und der Unterschied wird protokolliert.
|
||||||
|
MIT_QUELLE=0
|
||||||
|
if pulseaudio --start --exit-idle-time=-1 \
|
||||||
|
--load="module-sles-source" \
|
||||||
|
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \
|
||||||
|
>>"$LOG" 2>&1; then
|
||||||
|
MIT_QUELLE=1
|
||||||
|
ok "PulseAudio mit module-sles-source gestartet"
|
||||||
|
else
|
||||||
|
warn "module-sles-source ließ sich nicht laden — starte ohne"
|
||||||
|
try pulseaudio --start --exit-idle-time=-1 \
|
||||||
|
--load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1"
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
section "Verfügbare Quellen"
|
||||||
|
if have pactl; then
|
||||||
|
pactl list sources short 2>&1 | tee -a "$LOG" | sed 's/^/ /'
|
||||||
|
QUELLEN=$(pactl list sources short 2>/dev/null | wc -l)
|
||||||
|
if [ "$QUELLEN" -gt 0 ]; then
|
||||||
|
ok "$QUELLEN Aufnahmequelle(n) gemeldet"
|
||||||
|
else
|
||||||
|
fail "Keine einzige Aufnahmequelle — S2 wird so nicht gelingen"
|
||||||
|
hint "Falls module-sles-source fehlt, ist dieser Termux-Build ohne OpenSL-Aufnahme gebaut."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "pactl fehlt, kann Quellen nicht auflisten"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Aufnahme im Debian ───────────────────────────────────────────────────────
|
||||||
|
section "Aufnahme aus dem Debian heraus"
|
||||||
|
log " ${DAUER} Sekunden werden aufgenommen — bitte jetzt laut und deutlich sprechen."
|
||||||
|
log ""
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc "
|
||||||
|
set -uo pipefail
|
||||||
|
export PULSE_SERVER=127.0.0.1
|
||||||
|
echo ' Quellen aus Sicht des Debian:'
|
||||||
|
pactl list sources short 2>&1 | sed 's/^/ /' || echo ' (pactl lieferte nichts)'
|
||||||
|
echo ' Nehme auf …'
|
||||||
|
timeout $((DAUER + 4)) parecord --channels=1 --rate=16000 --file-format=wav \
|
||||||
|
/hpos/spike/out/$(basename "$WAV") &
|
||||||
|
REC=\$!
|
||||||
|
sleep $DAUER
|
||||||
|
kill \$REC 2>/dev/null
|
||||||
|
wait \$REC 2>/dev/null
|
||||||
|
echo ' Aufnahme beendet.'
|
||||||
|
" 2>&1 | tee -a "$LOG"
|
||||||
|
|
||||||
|
# ── Auswertung ───────────────────────────────────────────────────────────────
|
||||||
|
# Eine Datei zu erzeugen beweist nichts: die häufigste Fehlerform ist eine
|
||||||
|
# formal korrekte WAV-Datei voller Stille. Deshalb Größe UND Inhalt prüfen.
|
||||||
|
section "Auswertung"
|
||||||
|
if [ ! -s "$WAV" ]; then
|
||||||
|
fail "Keine Aufnahmedatei entstanden → S2 gescheitert"
|
||||||
|
else
|
||||||
|
GROESSE=$(stat -c%s "$WAV" 2>/dev/null || echo 0)
|
||||||
|
ok "Datei vorhanden: $WAV ($GROESSE Bytes)"
|
||||||
|
ERWARTET=$((DAUER * 16000 * 2 * 8 / 10))
|
||||||
|
if [ "$GROESSE" -lt "$ERWARTET" ]; then
|
||||||
|
warn "Deutlich kleiner als erwartet (~$ERWARTET Bytes) — vermutlich abgebrochen"
|
||||||
|
fi
|
||||||
|
# Stilletest. Der typische Fehlschlag ist eine formal korrekte WAV-Datei voller
|
||||||
|
# Nullen — sie sieht in jeder Dateiliste richtig aus und enthält doch nichts.
|
||||||
|
# Deshalb wird der Pegel gemessen, nicht nur die Größe.
|
||||||
|
if have python; then
|
||||||
|
python - "$WAV" <<'PY' 2>&1 | tee -a "$LOG"
|
||||||
|
import array, math, sys, wave
|
||||||
|
|
||||||
|
# Bewusst ohne audioop: das Modul ist in Python 3.13 entfernt worden, und Termux
|
||||||
|
# liegt regelmäßig auf der aktuellen Version.
|
||||||
|
try:
|
||||||
|
with wave.open(sys.argv[1]) as w:
|
||||||
|
breite = w.getsampwidth()
|
||||||
|
roh = w.readframes(w.getnframes())
|
||||||
|
if breite == 1:
|
||||||
|
werte = array.array("b", bytes(b - 128 for b in roh))
|
||||||
|
elif breite == 2:
|
||||||
|
werte = array.array("h", roh[: len(roh) // 2 * 2])
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{breite} Byte je Abtastwert werden hier nicht ausgewertet")
|
||||||
|
if not werte:
|
||||||
|
raise ValueError("keine Abtastwerte enthalten")
|
||||||
|
pegel = int(math.sqrt(sum(v * v for v in werte) / len(werte)))
|
||||||
|
spitze = max(abs(v) for v in werte)
|
||||||
|
print(f" Effektivpegel: {pegel} Spitze: {spitze}")
|
||||||
|
if spitze < 40:
|
||||||
|
print(" ✗ Praktisch Stille — die Quelle liefert keine echten Daten.")
|
||||||
|
print(" Das ist der typische Fehlschlag: Datei ja, Ton nein.")
|
||||||
|
print(" Prüfen: Mikrofonberechtigung für Termux:API, und ob")
|
||||||
|
print(" module-sles-source in diesem Termux-Build überhaupt existiert.")
|
||||||
|
else:
|
||||||
|
print(" ✓ Echtes Signal aufgenommen.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ! Datei nicht auswertbar: {e}")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
log ""
|
||||||
|
log " Zum Gegenhören: pactl upload-sample nicht nötig — einfach abspielen:"
|
||||||
|
log " proot-distro login $DISTRO -- env PULSE_SERVER=127.0.0.1 paplay /hpos/spike/out/$(basename "$WAV")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log " module-sles-source geladen: $([ "$MIT_QUELLE" = 1 ] && echo ja || echo NEIN)"
|
||||||
|
|
||||||
|
verdict "S2" "Ist auf der Aufnahme deine Stimme hörbar?
|
||||||
|
Abspielen mit dem Befehl oben. Eine Datei ohne Ton zählt als gescheitert."
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 0 · Beweis S3 — Virtuelle Kamera ohne Kernel-Modul.
|
||||||
|
#
|
||||||
|
# v4l2loopback wäre der klassische Weg, ist aber ein Kernel-Modul und damit ohne Root
|
||||||
|
# ausgeschlossen. Die Wette: ein GStreamer-pipewiresink mit media.class=Video/Source
|
||||||
|
# erzeugt einen Kamera-Node komplett im Userspace, den Firefox über das Portal annimmt.
|
||||||
|
#
|
||||||
|
# Belegt der Beweis, funktionieren Videocalls im Browser und in Element.
|
||||||
|
# Siehe: https://www.ideasonboard.com/news/pipewire-is-the-new-v4l2loopback/
|
||||||
|
#
|
||||||
|
# Ausführen IN TERMUX: bash spike/03-s3-pipewire-kamera.sh
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/lib.sh"
|
||||||
|
require_termux
|
||||||
|
|
||||||
|
DISTRO=debian
|
||||||
|
|
||||||
|
log "HPOS Phase 0 · S3 — Virtuelle PipeWire-Kamera"
|
||||||
|
log "Protokoll: $LOG"
|
||||||
|
log ""
|
||||||
|
log "Voraussetzung: ein laufender X-Server aus S1."
|
||||||
|
log "Falls nicht, in einer zweiten Sitzung: termux-x11 :0 &"
|
||||||
|
log ""
|
||||||
|
|
||||||
|
section "Übergabe an Debian"
|
||||||
|
proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||||
|
bash /hpos/spike/inner/s3-kamera.sh 2>&1 | tee -a "$LOG"
|
||||||
|
|
||||||
|
verdict "S3" "Zeigt Firefox auf webcamtests.com ein Bild aus der Quelle 'HPOS-Testkamera'?
|
||||||
|
Ein reines Auftauchen im Auswahlmenü genügt nicht — es muss Bild kommen."
|
||||||
Executable
+96
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Phase 0 · Beweis S4 — Intent-Handoff vom Linux zur Android-App.
|
||||||
|
#
|
||||||
|
# Die Wette: statt Android-Apps ins Linux zu streamen, übergibt das Linux eine
|
||||||
|
# Absicht und die App öffnet nativ im Vollbild. Für Navigation ist das nicht der
|
||||||
|
# Kompromiss, sondern die bessere Lösung — volles GPU, volles GPS, und Overlay-Apps
|
||||||
|
# funktionieren, was über einem gestreamten Fenster gar nicht ginge.
|
||||||
|
#
|
||||||
|
# Gebaut wird das nicht pro App, sondern einmal als xdg-open-Handler. Damit löst
|
||||||
|
# jede Linux-Anwendung — Adressbuch, Browser, Kalender — die richtige Android-App aus,
|
||||||
|
# ohne selbst etwas von der Bridge zu wissen.
|
||||||
|
#
|
||||||
|
# Ausführen IN TERMUX: bash spike/04-s4-intent-handoff.sh
|
||||||
|
set -uo pipefail
|
||||||
|
source "$(dirname "$0")/lib.sh"
|
||||||
|
require_termux
|
||||||
|
|
||||||
|
DISTRO=debian
|
||||||
|
ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}"
|
||||||
|
# Paketname einer Begleit-App mit Overlay, die vor der Navigation starten soll.
|
||||||
|
# Beispiel: HPOS_BEGLEIT_APP=de.blitzer bash spike/04-s4-intent-handoff.sh
|
||||||
|
BEGLEIT_APP="${HPOS_BEGLEIT_APP:-}"
|
||||||
|
|
||||||
|
log "HPOS Phase 0 · S4 — Intent-Handoff"
|
||||||
|
log "Navigationsziel: $ZIEL"
|
||||||
|
log "Begleit-App: ${BEGLEIT_APP:-(keine)}"
|
||||||
|
log "Protokoll: $LOG"
|
||||||
|
|
||||||
|
# ── Vorbedingungen ───────────────────────────────────────────────────────────
|
||||||
|
section "Vorbedingungen"
|
||||||
|
if have am; then
|
||||||
|
ok "am vorhanden"
|
||||||
|
else
|
||||||
|
fail "am fehlt (Paket termux-tools) — ohne das geht nichts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
have python && ok "python vorhanden" || { fail "python fehlt"; exit 1; }
|
||||||
|
|
||||||
|
if [ -n "$BEGLEIT_APP" ]; then
|
||||||
|
if pm list packages 2>/dev/null | grep -q "package:$BEGLEIT_APP"; then
|
||||||
|
ok "Begleit-App $BEGLEIT_APP ist installiert"
|
||||||
|
hint "Overlay-Berechtigung nötig: Einstellungen → Apps → $BEGLEIT_APP → über anderen Apps anzeigen"
|
||||||
|
else
|
||||||
|
warn "Begleit-App $BEGLEIT_APP nicht gefunden — Overlay-Teil des Beweises entfällt"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Mini-Bridge starten ──────────────────────────────────────────────────────
|
||||||
|
section "Mini-Bridge"
|
||||||
|
pkill -f 'bridge-mini.py' 2>/dev/null && { hint "alte Instanz beendet"; sleep 1; } || true
|
||||||
|
|
||||||
|
HPOS_BEGLEIT_APP="$BEGLEIT_APP" python "$REPO_ROOT/spike/bridge-mini.py" \
|
||||||
|
>>"$OUT_DIR/bridge-mini.log" 2>&1 &
|
||||||
|
BRIDGE_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if kill -0 "$BRIDGE_PID" 2>/dev/null; then
|
||||||
|
ok "Bridge läuft auf 127.0.0.1:8474 (PID $BRIDGE_PID)"
|
||||||
|
else
|
||||||
|
fail "Bridge startet nicht:"
|
||||||
|
tail -n 20 "$OUT_DIR/bridge-mini.log" | sed 's/^/ /' | tee -a "$LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Direkttest, bevor Linux ins Spiel kommt ──────────────────────────────────
|
||||||
|
# Erst beweisen, dass der Intent überhaupt zieht. Scheitert es hier, liegt es an
|
||||||
|
# Android und nicht an der Brücke ins Linux — ein wichtiger Unterschied.
|
||||||
|
section "Direkttest ohne Linux"
|
||||||
|
TOKEN="$(cat "$OUT_DIR/bridge-token")"
|
||||||
|
ANTWORT="$(printf '%s\t%s\n' "$TOKEN" "geo:0,0?q=$(python -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$ZIEL")" \
|
||||||
|
| timeout 25 python -c '
|
||||||
|
import socket,sys
|
||||||
|
s=socket.create_connection(("127.0.0.1",8474),10)
|
||||||
|
s.sendall(sys.stdin.buffer.read())
|
||||||
|
s.shutdown(socket.SHUT_WR)
|
||||||
|
print(s.recv(4096).decode().strip())
|
||||||
|
' 2>&1)"
|
||||||
|
log " Antwort: $ANTWORT"
|
||||||
|
case "$ANTWORT" in
|
||||||
|
OK*) ok "Android hat den Intent angenommen — schau aufs Telefon" ;;
|
||||||
|
*) fail "Intent nicht angenommen"
|
||||||
|
hint "Ist eine Navigations-App installiert? Ohne Google Maps o. ä. gibt es keinen Empfänger." ;;
|
||||||
|
esac
|
||||||
|
sleep 6
|
||||||
|
|
||||||
|
# ── Der eigentliche Beweis: aus dem Linux heraus ─────────────────────────────
|
||||||
|
section "Aus dem Linux heraus"
|
||||||
|
HPOS_ZIEL="$ZIEL" proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \
|
||||||
|
bash /hpos/spike/inner/s4-handoff.sh 2>&1 | tee -a "$LOG"
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log " Bridge-Protokoll: $OUT_DIR/bridge-mini.log"
|
||||||
|
log " Bridge beenden: kill $BRIDGE_PID"
|
||||||
|
|
||||||
|
verdict "S4" "Hat 'xdg-open geo:…' aus dem Debian heraus die Navigation auf dem Telefon gestartet?
|
||||||
|
Und lag — falls konfiguriert — das Overlay der Begleit-App darüber?"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Phase 0 — Machbarkeits-Spike
|
||||||
|
|
||||||
|
Vier Annahmen tragen das gesamte Projekt. Scheitert eine, sieht alles Weitere anders
|
||||||
|
aus — und das will man **vor** dem Bau der Infrastruktur wissen, nicht danach.
|
||||||
|
|
||||||
|
Der Code hier ist bewusst Wegwerf-Code. Was zählt, sind die Protokolle: sie werden
|
||||||
|
später zur Grundlage der Setup-Skripte in `rootfs/`.
|
||||||
|
|
||||||
|
| | Beweis | Wenn er scheitert |
|
||||||
|
|---|---|---|
|
||||||
|
| **S1** | Plasma Mobile läuft in proot (genestetes KWin) | XFCE-Profil wird Hauptweg; Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche ändern sich mit |
|
||||||
|
| **S2** | Mikrofon-Eingang erreicht die Linux-Seite | Keine Videocalls, keine Sprachaufnahme, keine Spracheingabe |
|
||||||
|
| **S3** | Virtuelle PipeWire-Kamera ohne Kernel-Modul | Kamera nur in einer eigenständigen App, keine Browser-Videocalls |
|
||||||
|
| **S4** | Intent-Handoff Linux → Android-App | Keine Navigation aus dem Linux-Adressbuch |
|
||||||
|
|
||||||
|
Dazu der **Übernacht-Test**: überlebt der Stack eine Nacht mit gesperrtem Bildschirm?
|
||||||
|
Scheitert er, ist das ein früher Warnschuss fürs Gesamtkonzept, kein Detail.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Android-Telefon, Adreno 6xx/7xx bevorzugt (Turnip-Pfad)
|
||||||
|
- **Termux von F-Droid oder GitHub** — nicht aus dem Play Store, die Fassung dort ist
|
||||||
|
veraltet und unbrauchbar
|
||||||
|
- Einmalig `provision/provision.sh` vom PC, plus die dort genannten Handgriffe von Hand
|
||||||
|
- Etwa 6 GB freier Speicher (Debian-Rootfs, Plasma Mobile, Firefox)
|
||||||
|
- Geduld beim ersten Lauf: rund 1 GB Pakete
|
||||||
|
|
||||||
|
## Ablauf
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Einmalig, vom PC mit angeschlossenem Telefon
|
||||||
|
./provision/provision.sh
|
||||||
|
|
||||||
|
# Auf dem Telefon in Termux
|
||||||
|
bash spike/00-bootstrap-termux.sh
|
||||||
|
|
||||||
|
# Beweise in dieser Reihenfolge — S1 zuerst, weil er der wackligste ist
|
||||||
|
bash spike/01-s1-plasma-mobile.sh
|
||||||
|
bash spike/02-s2-mikrofon.sh
|
||||||
|
bash spike/03-s3-pipewire-kamera.sh
|
||||||
|
bash spike/04-s4-intent-handoff.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Jedes Skript fragt am Ende nach deiner Beurteilung und schreibt sie nach
|
||||||
|
`spike/out/ergebnisse.tsv`. Rohprotokolle liegen daneben in `spike/out/*.log`.
|
||||||
|
|
||||||
|
**Übertrage die Ergebnisse anschließend nach [`docs/spike-protokoll.md`](../docs/spike-protokoll.md).**
|
||||||
|
`spike/out/` ist absichtlich nicht im Repo — die Auswertung gehört dorthin, nicht die Rohdaten.
|
||||||
|
|
||||||
|
## Stellschrauben
|
||||||
|
|
||||||
|
| Variable | Vorgabe | Wofür |
|
||||||
|
|---|---|---|
|
||||||
|
| `HPOS_BREITE` / `HPOS_HOEHE` | `1080` / `2340` | Virtuelle Auflösung. Volle Panel-Auflösung ist für einen Telefon-Desktop zu viel Fläche |
|
||||||
|
| `HPOS_DAUER` | `6` | Sekunden Aufnahme in S2 |
|
||||||
|
| `HPOS_ZIEL` | Brandenburger Tor, Berlin | Navigationsziel in S4 |
|
||||||
|
| `HPOS_BEGLEIT_APP` | leer | Paketname einer Overlay-App, die in S4 vor der Navigation startet |
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HPOS_BEGLEIT_APP=de.blitzer HPOS_ZIEL="Kölner Dom" bash spike/04-s4-intent-handoff.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wenn etwas schiefgeht
|
||||||
|
|
||||||
|
**S1 startet nicht.** Das Skript probiert von selbst zwei Wege und fällt am Ende auf
|
||||||
|
XFCE zurück. Läuft XFCE, dann tragen X-Server und GPU-Pfad — der Fehlschlag liegt dann
|
||||||
|
eindeutig bei Plasma. Läuft auch XFCE nicht, liegt es an X-Server oder GPU.
|
||||||
|
|
||||||
|
**S2 erzeugt eine Datei ohne Ton.** Der häufigste und tückischste Fehlschlag. Das Skript
|
||||||
|
misst deshalb den Pegel. Ursache ist meist eine fehlende Mikrofonberechtigung für
|
||||||
|
Termux:API oder ein Termux-Build ohne `module-sles-source`.
|
||||||
|
|
||||||
|
**S3: Firefox sieht die Kamera nicht.** Prüfen, ob `xdg-desktop-portal-kde` läuft — die
|
||||||
|
GTK- und wlr-Portale können das Kamera-Portal nicht bedienen. In `about:config` muss
|
||||||
|
`media.webrtc.camera.allow-pipewire` auf `true` stehen; das Skript setzt es vorab.
|
||||||
|
|
||||||
|
**S4: kein Empfänger für den Intent.** Ohne installierte Navigations-App gibt es nichts,
|
||||||
|
was den `geo:`-Intent annehmen könnte. Das ist kein Fehler der Brücke.
|
||||||
|
|
||||||
|
## Aufräumen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pkill -f 'termux-x11|virgl_test_server|kwin_wayland|pipewire|bridge-mini.py'
|
||||||
|
pulseaudio --kill
|
||||||
|
```
|
||||||
Executable
+168
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""HPOS Mini-Bridge — Prototyp des Intent-Handoffs für Beweis S4.
|
||||||
|
|
||||||
|
Läuft in Termux, lauscht auf 127.0.0.1 und übersetzt URIs vom Linux in
|
||||||
|
Android-Intents. Das ist die kleinste ehrliche Fassung dessen, was später die
|
||||||
|
Bridge-APK macht: sie speichert nichts, sie reicht durch.
|
||||||
|
|
||||||
|
Bewusst eng gehalten:
|
||||||
|
* nur 127.0.0.1, niemals extern erreichbar
|
||||||
|
* gemeinsames Token, damit nicht jede App auf dem Telefon Intents auslösen kann
|
||||||
|
* feste Liste erlaubter Schemata statt beliebiger Intents
|
||||||
|
|
||||||
|
Start: python spike/bridge-mini.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import socketserver
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
HOST = "127.0.0.1"
|
||||||
|
PORT = int(os.environ.get("HPOS_BRIDGE_PORT", "8474"))
|
||||||
|
|
||||||
|
BASIS = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
TOKEN_DATEI = os.path.join(BASIS, "out", "bridge-token")
|
||||||
|
|
||||||
|
# Paketname einer Begleit-App, die vor der Hauptanwendung starten soll — typisch
|
||||||
|
# eine App mit Overlay, das über der Navigation liegen muss. Leer = keine.
|
||||||
|
BEGLEIT_APP = os.environ.get("HPOS_BEGLEIT_APP", "").strip()
|
||||||
|
BEGLEIT_VORLAUF = float(os.environ.get("HPOS_BEGLEIT_VORLAUF", "2.5"))
|
||||||
|
|
||||||
|
# Was aus dem Linux kommen darf. Alles andere wird abgelehnt — eine offene
|
||||||
|
# Intent-Schnittstelle wäre ein Einfallstor für jede App auf dem Gerät.
|
||||||
|
ERLAUBTE_SCHEMATA = {"geo", "tel", "sms", "smsto", "mailto", "http", "https"}
|
||||||
|
|
||||||
|
|
||||||
|
def token_lesen() -> str:
|
||||||
|
"""Token holen, beim ersten Start erzeugen."""
|
||||||
|
os.makedirs(os.path.dirname(TOKEN_DATEI), exist_ok=True)
|
||||||
|
if not os.path.exists(TOKEN_DATEI):
|
||||||
|
with open(os.open(TOKEN_DATEI, os.O_CREAT | os.O_WRONLY, 0o600), "w") as f:
|
||||||
|
f.write(os.urandom(16).hex())
|
||||||
|
with open(TOKEN_DATEI) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
TOKEN = token_lesen()
|
||||||
|
|
||||||
|
|
||||||
|
def am(*args: str) -> tuple[int, str]:
|
||||||
|
"""`am` aufrufen und Ergebnis zurückgeben."""
|
||||||
|
befehl = ["am", *args]
|
||||||
|
print(f" → {' '.join(shlex.quote(a) for a in befehl)}", flush=True)
|
||||||
|
try:
|
||||||
|
p = subprocess.run(befehl, capture_output=True, text=True, timeout=20)
|
||||||
|
return p.returncode, (p.stdout + p.stderr).strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return 127, "am nicht gefunden — Paket termux-tools fehlt"
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return 124, "am hat nicht geantwortet"
|
||||||
|
|
||||||
|
|
||||||
|
def app_starten(paket: str) -> None:
|
||||||
|
am("start", "--user", "0", "-a", "android.intent.action.MAIN",
|
||||||
|
"-c", "android.intent.category.LAUNCHER", "-p", paket)
|
||||||
|
|
||||||
|
|
||||||
|
def navigation(ziel: str) -> tuple[int, str]:
|
||||||
|
"""Turn-by-turn direkt starten statt nur die Karte zu zeigen.
|
||||||
|
|
||||||
|
Vorher optional die Begleit-App, damit deren Overlay schon steht, wenn die
|
||||||
|
Navigation in den Vordergrund kommt — andersherum legt sich die Navigation
|
||||||
|
darüber und das Overlay ist verdeckt.
|
||||||
|
"""
|
||||||
|
if BEGLEIT_APP:
|
||||||
|
print(f" Begleit-App vorab: {BEGLEIT_APP}", flush=True)
|
||||||
|
app_starten(BEGLEIT_APP)
|
||||||
|
time.sleep(BEGLEIT_VORLAUF)
|
||||||
|
uri = "google.navigation:q=" + urllib.parse.quote(ziel) + "&mode=d"
|
||||||
|
return am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri)
|
||||||
|
|
||||||
|
|
||||||
|
def behandeln(zeile: str) -> str:
|
||||||
|
"""Eine Anfrage verarbeiten. Format: <token>\\t<uri>"""
|
||||||
|
teile = zeile.split("\t", 1)
|
||||||
|
if len(teile) != 2:
|
||||||
|
return "FEHLER ungueltiges-format"
|
||||||
|
token, uri = teile[0].strip(), teile[1].strip()
|
||||||
|
|
||||||
|
if token != TOKEN:
|
||||||
|
print(" ! Anfrage mit falschem Token abgelehnt", flush=True)
|
||||||
|
return "FEHLER token"
|
||||||
|
if not uri:
|
||||||
|
return "FEHLER leere-uri"
|
||||||
|
|
||||||
|
schema = uri.split(":", 1)[0].lower()
|
||||||
|
|
||||||
|
# geo:-URIs mit Suchbegriff bedeuten in der Praxis fast immer "dahin navigieren",
|
||||||
|
# nicht "Karte anschauen". Deshalb wird daraus Turn-by-turn.
|
||||||
|
if schema == "geo":
|
||||||
|
query = urllib.parse.parse_qs(urllib.parse.urlparse(uri).query).get("q", [""])[0]
|
||||||
|
if query:
|
||||||
|
rc, aus = navigation(query)
|
||||||
|
return f"OK navigation {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||||
|
|
||||||
|
if schema == "tel":
|
||||||
|
# DIAL statt CALL: die Nummer erscheint im Wähler, gewählt wird bewusst.
|
||||||
|
rc, aus = am("start", "--user", "0", "-a", "android.intent.action.DIAL", "-d", uri)
|
||||||
|
return f"OK dial {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||||
|
|
||||||
|
if schema in ERLAUBTE_SCHEMATA:
|
||||||
|
rc, aus = am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri)
|
||||||
|
return f"OK view {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}"
|
||||||
|
|
||||||
|
print(f" ! Schema abgelehnt: {schema}", flush=True)
|
||||||
|
return f"FEHLER schema-nicht-erlaubt {schema}"
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(socketserver.StreamRequestHandler):
|
||||||
|
timeout = 10
|
||||||
|
|
||||||
|
def handle(self) -> None:
|
||||||
|
try:
|
||||||
|
zeile = self.rfile.readline(4096).decode("utf-8", "replace")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ! Lesefehler: {e}", flush=True)
|
||||||
|
return
|
||||||
|
if not zeile:
|
||||||
|
return
|
||||||
|
antwort = behandeln(zeile)
|
||||||
|
print(f" ← {antwort}", flush=True)
|
||||||
|
try:
|
||||||
|
self.wfile.write((antwort + "\n").encode())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Server(socketserver.ThreadingTCPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
daemon_threads = True
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
print("HPOS Mini-Bridge (Prototyp für Beweis S4)")
|
||||||
|
print(f" Lauscht auf {HOST}:{PORT}")
|
||||||
|
print(f" Token-Datei {TOKEN_DATEI}")
|
||||||
|
print(f" Begleit-App {BEGLEIT_APP or '(keine)'}")
|
||||||
|
print(" Beenden mit Strg+C")
|
||||||
|
print()
|
||||||
|
try:
|
||||||
|
with Server((HOST, PORT), Handler) as srv:
|
||||||
|
srv.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nBeendet.")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Start fehlgeschlagen: {e}", file=sys.stderr)
|
||||||
|
print(f"Läuft schon etwas auf Port {PORT}?", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Executable
+139
@@ -0,0 +1,139 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 01-s1-plasma-mobile.sh.
|
||||||
|
# Direkt aufrufen ergibt keinen Sinn — die Umgebung kommt vom äußeren Skript.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}"
|
||||||
|
BREITE="${HPOS_BREITE:-1080}"
|
||||||
|
HOEHE="${HPOS_HOEHE:-2340}"
|
||||||
|
|
||||||
|
echo "── Debian-Seite: Plasma Mobile ──"
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
export DISPLAY=:0
|
||||||
|
export XDG_RUNTIME_DIR=/tmp/hpos-runtime
|
||||||
|
export PULSE_SERVER=127.0.0.1
|
||||||
|
export LANG=de_DE.UTF-8
|
||||||
|
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
|
# Plasma Mobile ist Touch-first: ohne diese Variablen kommt eine Desktop-Skalierung
|
||||||
|
# heraus, die auf einem Telefon unbedienbar ist.
|
||||||
|
export QT_QPA_PLATFORM=wayland
|
||||||
|
export QT_QUICK_CONTROLS_MOBILE=1
|
||||||
|
export PLASMA_PLATFORM=phone
|
||||||
|
export KDE_FULL_SESSION=true
|
||||||
|
|
||||||
|
case "$GPU_PROFIL" in
|
||||||
|
turnip)
|
||||||
|
# Zink übersetzt OpenGL auf Vulkan, Turnip liefert Vulkan direkt aus dem
|
||||||
|
# Adreno-Treiber — zusammen der schnellste Weg ohne Root.
|
||||||
|
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||||
|
export GALLIUM_DRIVER=zink
|
||||||
|
export TU_DEBUG=noconform
|
||||||
|
;;
|
||||||
|
virgl)
|
||||||
|
export GALLIUM_DRIVER=virpipe
|
||||||
|
export MESA_GL_VERSION_OVERRIDE=4.3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo " GPU-Profil: $GPU_PROFIL"
|
||||||
|
|
||||||
|
# ── Pakete ───────────────────────────────────────────────────────────────────
|
||||||
|
# Bewusst ohne empfohlene Pakete: sonst zieht Plasma Mobile eine komplette
|
||||||
|
# Desktop-Umgebung samt Drucksystem mit, was auf einem Telefon nur Platz kostet.
|
||||||
|
if ! command -v kwin_wayland >/dev/null 2>&1; then
|
||||||
|
echo "── Installiere Plasma Mobile (einmalig, gut 1 GB) ──"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
plasma-mobile kwin-wayland plasma-workspace maliit-keyboard \
|
||||||
|
xdg-desktop-portal-kde qtwayland5 libqt5waylandclient5 \
|
||||||
|
|| echo " ! Paketinstallation unvollständig — Details oben"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for b in kwin_wayland plasmashell; do
|
||||||
|
if command -v "$b" >/dev/null 2>&1; then echo " ✓ $b vorhanden"
|
||||||
|
else echo " ✗ $b fehlt"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── GPU-Nachweis, bevor der Desktop startet ──────────────────────────────────
|
||||||
|
# Wenn hier schon nichts kommt, ist ein Fehlschlag von Plasma keine Aussage über
|
||||||
|
# Plasma, sondern über den GPU-Pfad. Deshalb vorher messen.
|
||||||
|
echo "── GPU-Nachweis ──"
|
||||||
|
timeout 20 glxinfo -B 2>&1 | grep -Ei 'OpenGL (renderer|version)|Device' || echo " ! glxinfo lieferte nichts"
|
||||||
|
timeout 20 vulkaninfo --summary 2>&1 | grep -Ei 'driverName|deviceName' || echo " ! vulkaninfo lieferte nichts"
|
||||||
|
|
||||||
|
# ── D-Bus ────────────────────────────────────────────────────────────────────
|
||||||
|
if [ ! -e /run/dbus/system_bus_socket ]; then
|
||||||
|
mkdir -p /run/dbus
|
||||||
|
dbus-daemon --system --fork 2>/dev/null && echo " ✓ System-D-Bus gestartet" \
|
||||||
|
|| echo " ! System-D-Bus nicht gestartet (ohne logind oft verschmerzbar)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Versuch A: KWin genestet im X11-Backend ──────────────────────────────────
|
||||||
|
# Das ist die eigentliche Wette. In diesem Modus braucht KWin weder DRM noch Seat.
|
||||||
|
echo ""
|
||||||
|
echo "── Versuch A: kwin_wayland --x11-display ──"
|
||||||
|
LOGA=/tmp/hpos-s1-versuchA.log
|
||||||
|
dbus-run-session -- kwin_wayland \
|
||||||
|
--x11-display "$DISPLAY" \
|
||||||
|
--width "$BREITE" --height "$HOEHE" \
|
||||||
|
--xwayland \
|
||||||
|
-- plasmashell -p org.kde.plasma.phoneshell \
|
||||||
|
>"$LOGA" 2>&1 &
|
||||||
|
PID_A=$!
|
||||||
|
echo " gestartet (PID $PID_A), warte 25 s auf die Oberfläche …"
|
||||||
|
sleep 25
|
||||||
|
|
||||||
|
if kill -0 "$PID_A" 2>/dev/null; then
|
||||||
|
echo " ✓ Versuch A läuft noch — jetzt in die Termux:X11-App wechseln und schauen."
|
||||||
|
echo ""
|
||||||
|
echo " Beenden mit: pkill -f kwin_wayland"
|
||||||
|
echo " Protokoll: $LOGA"
|
||||||
|
tail -n 25 "$LOGA" 2>/dev/null | sed 's/^/ /'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ✗ Versuch A beendet. Letzte Zeilen:"
|
||||||
|
tail -n 40 "$LOGA" 2>/dev/null | sed 's/^/ /'
|
||||||
|
|
||||||
|
# ── Versuch B: die von Debian mitgelieferte Sitzung ──────────────────────────
|
||||||
|
# Startet KWin selbst und wählt sein Backend eigenständig. Weniger Kontrolle,
|
||||||
|
# aber manchmal weiß das Distributionsskript etwas, das wir nicht wissen.
|
||||||
|
echo ""
|
||||||
|
echo "── Versuch B: startplasmamobile ──"
|
||||||
|
if command -v startplasmamobile >/dev/null 2>&1; then
|
||||||
|
LOGB=/tmp/hpos-s1-versuchB.log
|
||||||
|
KWIN_COMPOSE=O2 dbus-run-session -- startplasmamobile >"$LOGB" 2>&1 &
|
||||||
|
PID_B=$!
|
||||||
|
sleep 25
|
||||||
|
if kill -0 "$PID_B" 2>/dev/null; then
|
||||||
|
echo " ✓ Versuch B läuft — in die Termux:X11-App wechseln."
|
||||||
|
tail -n 25 "$LOGB" 2>/dev/null | sed 's/^/ /'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo " ✗ Versuch B beendet. Letzte Zeilen:"
|
||||||
|
tail -n 40 "$LOGB" 2>/dev/null | sed 's/^/ /'
|
||||||
|
else
|
||||||
|
echo " ! startplasmamobile nicht vorhanden"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Rückfallebene: XFCE, damit der Abend nicht umsonst war ───────────────────
|
||||||
|
# Beweist zumindest, dass X-Server und GPU-Pfad tragen. Damit ist ein Fehlschlag
|
||||||
|
# von S1 eindeutig Plasma zuzuordnen und nicht der Umgebung.
|
||||||
|
echo ""
|
||||||
|
echo "── Rückfallebene: XFCE zur Gegenprobe ──"
|
||||||
|
if ! command -v startxfce4 >/dev/null 2>&1; then
|
||||||
|
apt-get install -y --no-install-recommends xfce4 xfce4-terminal >/dev/null 2>&1 \
|
||||||
|
|| echo " ! XFCE-Installation fehlgeschlagen"
|
||||||
|
fi
|
||||||
|
if command -v startxfce4 >/dev/null 2>&1; then
|
||||||
|
dbus-run-session -- startxfce4 >/tmp/hpos-s1-xfce.log 2>&1 &
|
||||||
|
sleep 12
|
||||||
|
if pgrep -f xfwm4 >/dev/null 2>&1; then
|
||||||
|
echo " ✓ XFCE läuft. S1 ist gescheitert, aber X-Server und GPU-Pfad tragen."
|
||||||
|
echo " → Im Protokoll als 'S1 gescheitert, XFCE-Profil wird Hauptweg' vermerken."
|
||||||
|
else
|
||||||
|
echo " ✗ Auch XFCE startet nicht — dann liegt es an X-Server oder GPU, nicht an Plasma."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 03-s3-pipewire-kamera.sh.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
export DISPLAY=:0
|
||||||
|
export XDG_RUNTIME_DIR=/tmp/hpos-runtime
|
||||||
|
export XDG_CURRENT_DESKTOP=KDE
|
||||||
|
export LANG=de_DE.UTF-8
|
||||||
|
mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
|
echo "── Debian-Seite: virtuelle PipeWire-Kamera ──"
|
||||||
|
|
||||||
|
# ── Pakete ───────────────────────────────────────────────────────────────────
|
||||||
|
# xdg-desktop-portal-kde ist der Grund, warum Plasma Mobile hier im Vorteil ist:
|
||||||
|
# die GTK- und wlr-Portale können das Kamera-Portal nicht bedienen.
|
||||||
|
if ! command -v pw-cli >/dev/null 2>&1; then
|
||||||
|
echo "── Installiere PipeWire und GStreamer (einmalig) ──"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
pipewire pipewire-pulse wireplumber pipewire-bin \
|
||||||
|
gstreamer1.0-pipewire gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
||||||
|
gstreamer1.0-tools xdg-desktop-portal xdg-desktop-portal-kde \
|
||||||
|
|| echo " ! Paketinstallation unvollständig"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── PipeWire starten ─────────────────────────────────────────────────────────
|
||||||
|
# Kein systemd im proot, also die Dienste von Hand — und in dieser Reihenfolge,
|
||||||
|
# sonst findet WirePlumber den Daemon nicht.
|
||||||
|
echo "── Dienste starten ──"
|
||||||
|
pkill -f 'pipewire|wireplumber|xdg-desktop-portal' 2>/dev/null; sleep 1
|
||||||
|
|
||||||
|
if [ ! -e /run/dbus/system_bus_socket ]; then
|
||||||
|
mkdir -p /run/dbus && dbus-daemon --system --fork 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
eval "$(dbus-launch --sh-syntax)" 2>/dev/null || true
|
||||||
|
|
||||||
|
pipewire >/tmp/hpos-pipewire.log 2>&1 & sleep 2
|
||||||
|
pipewire-pulse >/tmp/hpos-pw-pulse.log 2>&1 & sleep 1
|
||||||
|
wireplumber >/tmp/hpos-wireplumber.log 2>&1 & sleep 2
|
||||||
|
|
||||||
|
if pgrep -x pipewire >/dev/null; then echo " ✓ pipewire läuft"; else
|
||||||
|
echo " ✗ pipewire startet nicht:"; tail -n 20 /tmp/hpos-pipewire.log | sed 's/^/ /'; exit 1
|
||||||
|
fi
|
||||||
|
pgrep -x wireplumber >/dev/null && echo " ✓ wireplumber läuft" \
|
||||||
|
|| echo " ! wireplumber läuft nicht — Nodes werden evtl. nicht verwaltet"
|
||||||
|
|
||||||
|
/usr/libexec/xdg-desktop-portal-kde >/tmp/hpos-portal-kde.log 2>&1 & sleep 1
|
||||||
|
/usr/libexec/xdg-desktop-portal >/tmp/hpos-portal.log 2>&1 & sleep 2
|
||||||
|
pgrep -f xdg-desktop-portal >/dev/null && echo " ✓ Portal läuft" \
|
||||||
|
|| echo " ! Portal läuft nicht — Firefox wird die Kamera nicht sehen"
|
||||||
|
|
||||||
|
# ── Die virtuelle Kamera ─────────────────────────────────────────────────────
|
||||||
|
# Im Spike steht ein Testbild statt der echten Kamera. Das ist Absicht: bewiesen
|
||||||
|
# werden soll der PIPEWIRE-Weg, nicht die Bridge. Später ersetzt der Bridge-Stream
|
||||||
|
# videotestsrc — sonst nichts.
|
||||||
|
echo ""
|
||||||
|
echo "── Virtuelle Kamera starten ──"
|
||||||
|
gst-launch-1.0 -q videotestsrc pattern=ball is-live=true \
|
||||||
|
! video/x-raw,format=YUY2,width=640,height=480,framerate=30/1 \
|
||||||
|
! pipewiresink mode=provide \
|
||||||
|
stream-properties="props,media.class=Video/Source,node.description=HPOS-Testkamera,node.name=hpos_kamera" \
|
||||||
|
>/tmp/hpos-kamera.log 2>&1 &
|
||||||
|
GST_PID=$!
|
||||||
|
sleep 4
|
||||||
|
|
||||||
|
if ! kill -0 "$GST_PID" 2>/dev/null; then
|
||||||
|
echo " ✗ GStreamer sofort beendet:"
|
||||||
|
tail -n 25 /tmp/hpos-kamera.log | sed 's/^/ /'
|
||||||
|
echo " Häufigste Ursache: gstreamer1.0-pipewire fehlt."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✓ GStreamer läuft (PID $GST_PID)"
|
||||||
|
|
||||||
|
# ── Nachweis, dass der Node existiert ────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "── Node-Nachweis ──"
|
||||||
|
if command -v wpctl >/dev/null 2>&1; then
|
||||||
|
wpctl status 2>&1 | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
if pw-cli ls Node 2>/dev/null | grep -qi 'hpos_kamera\|HPOS-Testkamera'; then
|
||||||
|
echo " ✓ Kamera-Node ist in PipeWire registriert"
|
||||||
|
else
|
||||||
|
echo " ! Node nicht eindeutig gefunden — vollständige Liste:"
|
||||||
|
pw-cli ls Node 2>/dev/null | grep -Ei 'node.name|media.class' | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Firefox ──────────────────────────────────────────────────────────────────
|
||||||
|
# Ohne media.webrtc.camera.allow-pipewire fragt Firefox weiterhin nur V4L2 ab und
|
||||||
|
# findet folgerichtig nichts. Die Einstellung wird hier vorab ins Profil geschrieben,
|
||||||
|
# damit der Test nicht an einem vergessenen Häkchen scheitert.
|
||||||
|
echo ""
|
||||||
|
echo "── Firefox vorbereiten ──"
|
||||||
|
if ! command -v firefox >/dev/null 2>&1 && ! command -v firefox-esr >/dev/null 2>&1; then
|
||||||
|
apt-get install -y --no-install-recommends firefox-esr >/dev/null 2>&1 \
|
||||||
|
|| echo " ! Firefox-Installation fehlgeschlagen"
|
||||||
|
fi
|
||||||
|
FF=$(command -v firefox || command -v firefox-esr || true)
|
||||||
|
|
||||||
|
if [ -n "$FF" ]; then
|
||||||
|
PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1)
|
||||||
|
if [ -z "$PROFIL" ]; then
|
||||||
|
timeout 25 "$FF" --headless --screenshot /tmp/ff-init.png about:blank >/dev/null 2>&1 || true
|
||||||
|
PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1)
|
||||||
|
fi
|
||||||
|
if [ -n "$PROFIL" ]; then
|
||||||
|
{
|
||||||
|
echo 'user_pref("media.webrtc.camera.allow-pipewire", true);'
|
||||||
|
echo 'user_pref("widget.use-xdg-desktop-portal.camera", 1);'
|
||||||
|
} >>"$PROFIL/user.js"
|
||||||
|
echo " ✓ Einstellungen ins Profil geschrieben: $PROFIL"
|
||||||
|
else
|
||||||
|
echo " ! Kein Firefox-Profil gefunden — bitte manuell in about:config setzen:"
|
||||||
|
echo " media.webrtc.camera.allow-pipewire = true"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo " Starte Firefox mit dem Kameratest …"
|
||||||
|
"$FF" https://webcamtests.com/ >/tmp/hpos-firefox.log 2>&1 &
|
||||||
|
sleep 3
|
||||||
|
echo " → In der Termux:X11-App die Quelle 'HPOS-Testkamera' auswählen."
|
||||||
|
echo " Erwartet: ein springender Ball. Erscheint er, ist S3 bestanden."
|
||||||
|
else
|
||||||
|
echo " ! Ohne Firefox lässt sich der entscheidende Teil nicht prüfen."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Beenden mit: kill $GST_PID ; pkill -f 'pipewire|wireplumber'"
|
||||||
Executable
+106
@@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 04-s4-intent-handoff.sh.
|
||||||
|
#
|
||||||
|
# Richtet hpos-open als xdg-open-Handler für geo:, tel:, sms: und mailto: ein.
|
||||||
|
# Der Witz daran: nichts wird pro Anwendung gebaut. Adressbuch, Browser, Kalender
|
||||||
|
# und Dateimanager rufen alle xdg-open auf und landen dadurch automatisch hier.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}"
|
||||||
|
TOKEN_DATEI=/hpos/spike/out/bridge-token
|
||||||
|
|
||||||
|
echo "── Debian-Seite: Intent-Handoff ──"
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
command -v xdg-open >/dev/null 2>&1 || apt-get install -y --no-install-recommends xdg-utils >/dev/null 2>&1
|
||||||
|
|
||||||
|
if [ ! -r "$TOKEN_DATEI" ]; then
|
||||||
|
echo " ✗ Token-Datei nicht lesbar: $TOKEN_DATEI"
|
||||||
|
echo " Läuft die Mini-Bridge? Sie erzeugt das Token beim ersten Start."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── hpos-open ────────────────────────────────────────────────────────────────
|
||||||
|
# Der Handler selbst: nimmt eine URI, schickt sie an die Bridge, fertig. Später
|
||||||
|
# ersetzt die Bridge-APK den Gegenpart, dieses Skript bleibt wie es ist.
|
||||||
|
install -d /usr/local/bin
|
||||||
|
cat >/usr/local/bin/hpos-open <<'HANDLER'
|
||||||
|
#!/bin/sh
|
||||||
|
# Reicht eine URI an die HPOS-Bridge auf der Android-Seite weiter.
|
||||||
|
# Aufruf: hpos-open "geo:0,0?q=Adresse"
|
||||||
|
set -eu
|
||||||
|
URI="${1:-}"
|
||||||
|
[ -n "$URI" ] || { echo "Aufruf: hpos-open <uri>" >&2; exit 2; }
|
||||||
|
|
||||||
|
TOKEN_DATEI="${HPOS_TOKEN_DATEI:-/hpos/spike/out/bridge-token}"
|
||||||
|
[ -r "$TOKEN_DATEI" ] || { echo "hpos-open: kein Token unter $TOKEN_DATEI" >&2; exit 1; }
|
||||||
|
TOKEN="$(cat "$TOKEN_DATEI")"
|
||||||
|
|
||||||
|
printf '%s\t%s\n' "$TOKEN" "$URI" | timeout 20 python3 -c '
|
||||||
|
import socket, sys
|
||||||
|
try:
|
||||||
|
s = socket.create_connection(("127.0.0.1", 8474), 10)
|
||||||
|
except OSError as e:
|
||||||
|
sys.exit(f"hpos-open: Bridge nicht erreichbar ({e})")
|
||||||
|
s.sendall(sys.stdin.buffer.read())
|
||||||
|
s.shutdown(socket.SHUT_WR)
|
||||||
|
antwort = s.recv(4096).decode().strip()
|
||||||
|
print(antwort)
|
||||||
|
sys.exit(0 if antwort.startswith("OK") else 1)
|
||||||
|
'
|
||||||
|
HANDLER
|
||||||
|
chmod 755 /usr/local/bin/hpos-open
|
||||||
|
echo " ✓ /usr/local/bin/hpos-open eingerichtet"
|
||||||
|
|
||||||
|
# ── Als Standardanwendung registrieren ───────────────────────────────────────
|
||||||
|
cat >/usr/share/applications/hpos-open.desktop <<'DESKTOP'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Auf dem Telefon öffnen
|
||||||
|
Comment=Reicht Adressen, Rufnummern und Nachrichten an die Android-Seite weiter
|
||||||
|
Exec=hpos-open %u
|
||||||
|
Terminal=false
|
||||||
|
NoDisplay=true
|
||||||
|
MimeType=x-scheme-handler/geo;x-scheme-handler/tel;x-scheme-handler/sms;x-scheme-handler/smsto;x-scheme-handler/mailto;
|
||||||
|
DESKTOP
|
||||||
|
|
||||||
|
update-desktop-database /usr/share/applications >/dev/null 2>&1 || true
|
||||||
|
for schema in geo tel sms smsto; do
|
||||||
|
xdg-mime default hpos-open.desktop "x-scheme-handler/$schema" 2>/dev/null \
|
||||||
|
&& echo " ✓ $schema: → hpos-open" \
|
||||||
|
|| echo " ! $schema: Registrierung fehlgeschlagen"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo " Zugeordnet laut System:"
|
||||||
|
for schema in geo tel sms; do
|
||||||
|
printf ' %-6s %s\n' "$schema:" "$(xdg-mime query default "x-scheme-handler/$schema" 2>/dev/null || echo '?')"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Der Beweis ───────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "── Test 1: hpos-open direkt ──"
|
||||||
|
KODIERT=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' "$ZIEL")
|
||||||
|
if hpos-open "geo:0,0?q=$KODIERT"; then
|
||||||
|
echo " ✓ Bridge hat angenommen"
|
||||||
|
else
|
||||||
|
echo " ✗ Bridge hat abgelehnt oder war nicht erreichbar"
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "── Test 2: über xdg-open ──"
|
||||||
|
echo " Das ist der eigentliche Beweis: der Weg, den jede Linux-Anwendung nimmt."
|
||||||
|
if xdg-open "geo:0,0?q=$KODIERT" 2>&1; then
|
||||||
|
echo " ✓ xdg-open hat den Handler aufgerufen"
|
||||||
|
else
|
||||||
|
echo " ✗ xdg-open ist gescheitert"
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "── Test 3: Rufnummer ──"
|
||||||
|
hpos-open "tel:+4930901820" || echo " ! tel: fehlgeschlagen"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Jetzt aufs Telefon schauen: läuft die Navigation, liegt das Overlay darüber?"
|
||||||
|
echo " Zurück ins Linux über den Android-Task-Manager."
|
||||||
Executable
+105
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Gemeinsame Hilfsfunktionen für die Phase-0-Spike-Skripte.
|
||||||
|
# Wird von den 0x-Skripten eingebunden, nicht direkt ausgeführt.
|
||||||
|
|
||||||
|
# Farben nur wenn wir auf einem Terminal landen — sonst verschmutzen sie das Protokoll.
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_ERR=$'\033[31m'
|
||||||
|
C_HEAD=$'\033[1;36m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
|
||||||
|
else
|
||||||
|
C_OK=""; C_WARN=""; C_ERR=""; C_HEAD=""; C_DIM=""; C_OFF=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
OUT_DIR="$REPO_ROOT/spike/out"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
# Jeder Lauf bekommt sein eigenes Protokoll. Die Sammlung ist später die halbe
|
||||||
|
# Auswertung — deshalb wird alles mitgeschrieben, auch was funktioniert hat.
|
||||||
|
LOG="${LOG:-$OUT_DIR/$(basename "${0%.sh}").log}"
|
||||||
|
|
||||||
|
log() { printf '%s\n' "$*" | tee -a "$LOG"; }
|
||||||
|
section() { log ""; log "${C_HEAD}══ $* ══${C_OFF}"; }
|
||||||
|
ok() { log "${C_OK} ✓${C_OFF} $*"; }
|
||||||
|
warn() { log "${C_WARN} !${C_OFF} $*"; }
|
||||||
|
fail() { log "${C_ERR} ✗${C_OFF} $*"; }
|
||||||
|
hint() { log "${C_DIM} $*${C_OFF}"; }
|
||||||
|
|
||||||
|
# Befehl ausführen, Ausgabe ins Protokoll, Fehler nicht tödlich.
|
||||||
|
# Für alles, was scheitern DARF, ohne den Spike abzubrechen.
|
||||||
|
try() {
|
||||||
|
log "${C_DIM} \$ $*${C_OFF}"
|
||||||
|
if "$@" >>"$LOG" 2>&1; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
local rc=$?
|
||||||
|
warn "fehlgeschlagen (Code $rc): $*"
|
||||||
|
return $rc
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wie try(), aber ein Fehlschlag beendet den Spike. Für echte Voraussetzungen.
|
||||||
|
need() {
|
||||||
|
log "${C_DIM} \$ $*${C_OFF}"
|
||||||
|
if ! "$@" >>"$LOG" 2>&1; then
|
||||||
|
fail "Abbruch — notwendiger Schritt fehlgeschlagen: $*"
|
||||||
|
log ""
|
||||||
|
log "Vollständiges Protokoll: $LOG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
have() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
in_termux() {
|
||||||
|
case "${PREFIX:-}" in
|
||||||
|
*com.termux*) [ -d "$PREFIX/bin" ] ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
require_termux() {
|
||||||
|
if ! in_termux; then
|
||||||
|
fail "Dieses Skript läuft in Termux auf dem Telefon, nicht auf dem PC."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fragt am Ende jedes Beweises nach dem, was nur ein Mensch beurteilen kann:
|
||||||
|
# ob es tatsächlich funktioniert hat. Das Ergebnis geht ins Protokoll.
|
||||||
|
verdict() {
|
||||||
|
local beweis="$1" frage="$2"
|
||||||
|
log ""
|
||||||
|
log "${C_HEAD}── Beurteilung $beweis ──${C_OFF}"
|
||||||
|
log "$frage"
|
||||||
|
printf ' [j] ja [n] nein [t] teilweise > '
|
||||||
|
read -r antwort </dev/tty || antwort="?"
|
||||||
|
local text
|
||||||
|
case "$antwort" in
|
||||||
|
j|J) text="BESTANDEN" ;;
|
||||||
|
n|N) text="GESCHEITERT" ;;
|
||||||
|
t|T) text="TEILWEISE" ;;
|
||||||
|
*) text="UNKLAR" ;;
|
||||||
|
esac
|
||||||
|
printf ' Anmerkung (eine Zeile, Enter für nichts) > '
|
||||||
|
read -r notiz </dev/tty || notiz=""
|
||||||
|
log " Ergebnis $beweis: $text ${notiz:+— $notiz}"
|
||||||
|
printf '%s\t%s\t%s\n' "$beweis" "$text" "$notiz" >>"$OUT_DIR/ergebnisse.tsv"
|
||||||
|
log ""
|
||||||
|
log "Ins Protokoll übertragen: docs/spike-protokoll.md"
|
||||||
|
log "Rohdaten: $LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gerätesteckbrief — landet in jedem Protokoll, damit Meldungen vergleichbar sind.
|
||||||
|
geraete_steckbrief() {
|
||||||
|
section "Gerät"
|
||||||
|
local p
|
||||||
|
for p in ro.product.manufacturer ro.product.model ro.product.device \
|
||||||
|
ro.build.version.release ro.build.version.sdk ro.board.platform \
|
||||||
|
ro.hardware ro.soc.model; do
|
||||||
|
log " $p = $(getprop "$p" 2>/dev/null || echo '?')"
|
||||||
|
done
|
||||||
|
log " Arch = $(uname -m)"
|
||||||
|
log " Kernel = $(uname -r)"
|
||||||
|
if have free; then log " RAM = $(free -h 2>/dev/null | awk '/Mem:/{print $2}')"; fi
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user