https://www.eric-scheibler.de/Erics Blog2022-04-30T20:52:05ZEric Scheiblerhttps://www.eric-scheibler.detag:www.eric-scheibler.de,2022-04-30:/de/blog/2022/04/starte-den-android-screenreader-talkback-von-der-linux-kommandozeile/Starte den Android Screenreader Talkback von der Linux Kommandozeile2022-04-30T20:52:05Z2022-04-30T20:52:05Z<p>Mit dem folgenden Befehl lässt sich Androids Screenreader Talkback
barrierefrei vom Linux PC aus aktivieren:</p>
<pre><code>adb shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService</code></pre>
<p>Leider muss man auf dem Android Gerät zuvor einmal das <a
href="https://developer.android.com/studio/debug/dev-options">USB
Debugging</a> aktiviert und auf dem PC die <a
href="https://developer.android.com/studio/command-line/adb">Android
Debug Bridge</a> installiert haben. Sonst funktioniert es nicht.</p>
tag:www.eric-scheibler.de,2021-07-03:/de/blog/2021/07/shell-skript-fuer-adf-scanner-fujitsu-sp-1120/Shell Skript für ADF Scanner Fujitsu SP-11202021-07-03T21:10:31Z2021-07-03T21:10:31Z<p>Kürzlich habe ich einen <a
href="https://www.fujitsu.com/global/products/computing/peripheral/scanners/business/sp1120/">Fujitsu
SP-1120</a> gekauft, um meinen alten und langsamen Flachbettscanner zu
ersetzen. Der Fujitsu ist ein Scanner mit automatischem Dokumenteneinzug
(ADF) und Duplex Unterstützung. Er scannt deutlich schneller, erfasst
Vorer- und Rückseite auf einmal und liefert eine deutlich bessere
Bildqualität und somit erheblich genauere OCR Ergebnisse.</p>
<p>Dieser Artikel enthält eine Installationsanleitung für Debian Linux
und ein einfaches Shell Skript, welches einen Scan in eine durchsuchbare
PDF Datei konvertiert.</p>
<!-- more -->
<h2 id="scanner-installieren">Scanner installieren</h2>
<p>Zuerst muss das <a
href="https://imagescanner.fujitsu.com/global/dl/index-sps.html">Linux
Treiberpaket</a> heruntergeladen werden. Der Treiber wird offiziell für
Ubuntu angeboten, funktioniert aber mindestens auch unter Debian.</p>
<p>Anschließend startet man die Installation:</p>
<pre><code># dpkg -i pfusp-ubuntu18.04_2.1.1_amd64.deb</code></pre>
<p>und erzeugt die udev Regel <code>99-fujitsu-sp-1120.rules</code>
unter <code>/etc/udev/rules.d</code> mit dem folgenden Inhalt:</p>
<pre><code># Fujitsu ScanSnap SP-1120
ATTRS{idVendor}=="04c5", ATTRS{idProduct}=="1473", MODE="0664", GROUP="scanner", ENV{libsane_matched}="yes"</code></pre>
<p>Hat man ein anderes Scannermodell wie beispielsweise den SP-1125 oder
den SP-1130 gekauft, muss die Produkt-Id angepasst werden. Anschließend
die udev Regeln neu laden:</p>
<pre><code># udevadm control --reload
# udevadm trigger</code></pre>
<p>Schließlich noch sane (Scanner Access Now Easy) installieren und
überprüfen, ob der Scanner korrekt erkannt wird:</p>
<pre><code># apt install sane
# scanadf --list-devices</code></pre>
<p>Die Ausgabe sollte etwa wie folgt aussehen:</p>
<pre><code>device `pfusp:SP1120:001:093' is a FUJITSU SP1120 scanner</code></pre>
<h2 id="scanskript">Scanskript</h2>
<p>Als nächstes das weiter unten angebotene Shell Skript herunterladen,
ausführbar machen und ggf. ein paar Variablen anpassen. Beispielsweise
habe ich als PDF Betrachter ein anderes Shell Skript von mir angegeben,
welches sich <a
href="/de/blog/2015/04/skript-zum-extrahieren-von-text-aus-bildern-und-gescannten-pdf-dokumenten/">hier</a>
findet.</p>
<p>Einen Scan startet man anschließend wie folgt:</p>
<pre><code>adf</code></pre>
<p>Der Parameter <code>-p</code> zeigt eine Vorschau der ersten Seite,
bevor das gesamte Dokument verarbeitet wird und <code>-h</code> gibt
einen Überblick über alle Funktionen des Skripts.</p>
<p>Download: <a href="/downloads/adf">adf</a></p>
<pre><code>#!/bin/bash
# Script to control an ADF scanner
# - start scanning and create a single pdf file
# - with empty page and orientation detection
# - tested with Fujitsu SP-1120
#
# ... excessively borrowed from https://github.com/rocketraman/sane-scan-pdf
#
# Version: 0.1
# Date: 2021-06-16
# License: GNU General Public License
# Author: Eric Scheibler
# E-Mail: email [at] eric-scheibler [dot] de
# URL: http://eric-scheibler.de/en/blog/2015/04/script-to-extract-text-from-images-and-scanned-pdf-files/
#
# Install:
# sudo apt install imagemagick poppler-utils sane tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng unpaper
OUTPUT="scan.pdf"
TEXT_EDITOR="/usr/bin/vim"
PDF_VIEWER="$HOME/bin/ocr"
HELP=0
VERBOSE=0
# scanner params
DEVICE=pfusp
RESOLUTION=400
MODE=Lineart
# ocr params
OCR_LANGUAGE=deu
OCR_PREVIEW_FIRST_PAGE=0
OVERWRITE_OUTPUT_FILE=0
#####
TMP_DIR=$(mktemp -d -p "" scan.XXXXXXXXXX)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
function yes_or_no {
while true; do
read -p "$* [y/n]: " yn
case $yn in
[Yy]*) return 0 ;;
[Nn]*) echo "Aborted" ; return 1 ;;
esac
done
}
# Parse command-line options
while [[ $# > 0 ]]; do
case "$1" in
-h|--help) HELP=1 ;;
-v|--verbose) VERBOSE=1 ;;
-o|--output) shift; OUTPUT="$1" ;;
-x|--device) shift; DEVICE=$1;;
-m|--mode) shift; MODE=$1 ;;
-r|--resolution) shift; RESOLUTION=$1 ;;
-l|--language) shift; OCR_LANGUAGE=$1 ;;
-p|--preview-first-page) OCR_PREVIEW_FIRST_PAGE=1 ;;
-w|--overwrite-output-file) OVERWRITE_OUTPUT_FILE=1 ;;
esac
shift # next option
done
if [[ $HELP == 1 ]]; then
echo "$(basename $0) [OPTIONS]... [OUTPUT]"
echo ""
echo "OPTIONS"
echo " -x, --device"
echo " Override scanner device name, defaulting to \"pfusp\""
echo " -m, --mode"
echo " Mode e.g. Lineart (default), Halftone, Gray, Color, etc."
echo " -r, --resolution"
echo " Resolution e.g 300 (default)"
echo " -l, --language <lang>"
echo " which language to use for OCR"
echo " -p, --preview-first-page"
echo " OCR first page and preview in $TEXT_EDITOR"
echo ""
echo "OUTPUT"
echo " -o, --output <outputfile>"
echo " Output to named file default=scan.pdf"
echo " -w, --overwrite-output-file"
echo " Overwrite the output pdf file, if it already exists"
echo " -v, --verbose"
exit 0
fi
if [[ $VERBOSE == 0 ]]; then
quiet_paran="--quiet"
suppress_error_messages="2> /dev/null"
fi
if [[ "$OUTPUT" == "" ]]; then
echo >&2 "Output file must be specified. Aborting."
exit 1
fi
if [[ -f "$OUTPUT" ]]; then
if [[ $OVERWRITE_OUTPUT_FILE == 0 ]]; then
echo >&2 "Output file $OUTPUT already exists. Aborting."
exit 1
else
rm "$OUTPUT"
fi
fi
echo >&2 "Scanning..."
scanadf --device-name "$DEVICE" --source Adf-duplex --resolution $RESOLUTION --mode $MODE -o $TMP_DIR/scan-%04d
if [[ $? != 0 ]]; then
exit 1
fi
echo ""
shopt -s extglob nullglob
image_files=($TMP_DIR/scan-[0-9]*)
num_scans=${#image_files[@]}
if [[ $num_scans > 0 ]]; then
if [[ $OCR_PREVIEW_FIRST_PAGE == 1 ]]; then
echo "Creating preview..."
preview_image_file="${image_files[0]}"
preview_text_file="$TMP_DIR/preview_first_page.txt"
# ocr
eval tesseract $preview_image_file ${preview_text_file%.*} -l $OCR_LANGUAGE --psm 12 $suppress_error_messages
# show
$TEXT_EDITOR $preview_text_file
if ! yes_or_no "Proceed?"; then
exit 0
fi
# remove preview text file
rm $preview_text_file
echo ""
fi
echo "Processing $num_scans pages"
for image_file in ${image_files[@]}; do
echo "Process $(basename $image_file)"
# unpaper
eval unpaper $quiet_paran --overwrite --dpi $RESOLUTION $image_file $image_file $suppress_error_messages
# convert to tiff
convert -density ${RESOLUTION}x${RESOLUTION} -units PixelsPerInch $image_file ${image_file}.tiff
rm $image_file
# orientation detection
orientation_result=$(eval tesseract ${image_file}.tiff - --psm 0 $suppress_error_messages) || orientation_result=
if [[ $orientation_result == *"Rotate: 180"* ]]; then
echo "Image orientation is upside down, rotate"
convert -rotate 180 ${image_file}.tiff ${image_file}.tiff
fi
# empty page detection
percentage_white=$(convert ${image_file}.tiff -fuzz 0% -negate -threshold 0 -negate -format "%[fx:100*mean]" info:) || percentage_white=0
is_empty_page=$(echo "$percentage_white >= 99.8" | bc -l)
if [[ $is_empty_page == 1 && $orientation_result == "" ]]; then
echo "Empty page removed"
else
eval tesseract ${image_file}.tiff $image_file -l $OCR_LANGUAGE pdf $suppress_error_messages
rm ${image_file}.tiff
fi
echo ""
done
# rename or unite created pdf(s)
pdf_files=($TMP_DIR/scan-[0-9]*.pdf)
num_pdf_files=${#pdf_files[@]}
if [[ $num_pdf_files == 1 ]]; then
echo "Renaming..."
mv $TMP_DIR/scan-0*.pdf "$OUTPUT"
elif [[ $num_pdf_files > 1 ]]; then
echo "Concatenating pdfs..."
pdfunite "${pdf_files[@]}" "$OUTPUT"
fi
fi
if [[ -f "$OUTPUT" ]]; then
echo "Done."
if [[ $PDF_VIEWER != "" ]]; then
if yes_or_no "Open ${OUTPUT}?"; then
$PDF_VIEWER $OUTPUT
fi
fi
else
echo "No scans found."
fi
</code></pre>
tag:www.eric-scheibler.de,2021-06-12:/de/blog/2021/06/einfache-und-barrierefreie-netzwerkverkehranalyse/Einfache und barrierefreie Netzwerkverkehranalyse2021-06-12T12:56:47Z2021-06-12T12:56:47Z<p>Während der Entwicklung einer Android App musste ich unlängst die
abgesendeten Netzwerkanfragen analysieren. Unter Android selbst kam ich
nicht weiter. Die Analyse gestaltete sich aber unter Zuhilfenahme von <a
href="https://mitmproxy.org">mitmproxy</a> erstaunlich einfach.</p>
<p>Im folgenden Artikel beschreibe ich dessen Installation und die
Verwendung in Android Apps und wget.</p>
<!-- more -->
<p>Grundsätzlich funktioniert das wie ein <a
href="https://de.wikipedia.org/wiki/Man-in-the-Middle-Angriff">mitm
Angriff</a>. Man installiert einen Proxyserver innerhalb des lokalen
Netzwerks und konfiguriert die Clients, sodass sie ihre Anfragen über
den Proxy verschicken. Ich habe mich für mitmproxy entschieden, da es
für die Linux Konsole verfügbar, einfach zu benutzen und mit
Screenreadern wie <a href="https://brltty.app">brltty</a> recht gut
bedienbar ist.</p>
<p>Installation: <code>sudo apt install mitmproxy</code></p>
<p>Start:
<code>mitmproxy --listen-host 192.168.100.100 --listen-port 22222 --save-stream-file +requests.log</code></p>
<h2 id="wget">Wget</h2>
<p>Zunächst testweise eine http Anfrage:</p>
<pre><code>wget -e https_proxy=192.168.100.100:22222 http://example.org</code></pre>
<p>Diese sollte in der mitmproxy Hauptansicht auftauchen.</p>
<p>Wer auch verschlüsselte Anfragen analysieren möchte, muss in seinem
Client das SSL Zertifikat angeben, welches mitmproxy beim ersten Start
generiert hat. Man findet es unter
<code>~/.mitmproxy/mitmproxy-ca-cert.pem</code>. Die Datei muss auf den
Clientrechner kopiert und wie folgt verwendet werden:</p>
<pre><code>wget -e https_proxy=192.168.100.100:22222 --ca-certificate /path/to/mitmproxy-ca-cert.pem https://example.org</code></pre>
<p>Wenn alles geklappt hat, enthält die mitmproxy Hauptansicht nun zwei
Einträge. Mit den Pfeiltasten bewegt man sich durch die Liste, mit ENTER
öffnet man die Details.</p>
<p>Leider unterstützt die UI den Hardware Konsolencursor nicht. D.h.
Screenreader lesen den ausgewählten Listeneintrag beim navigieren nicht
korrekt vor. Wer brltty einsetzt, kann allerdings die autom.
Cursorverfolgung vorübergehend deaktivieren (CAPSLOCK+ENTER) und dem
ausgewählten Eintrag manuell folgen. Selbigen erkennt man an einem
vorangestellten “>>”.</p>
<p>Die Detailansicht besteht aus drei Tabs: Request, Response und
Details. Man wechselt entweder mit den Pfeiltasten links und rechts oder
via TAB durch die Reiter. Mich interessierten vor allem die Cookie
Header der Anfrage - daher wurde ich bereits im Requests-Tab fündig.</p>
<h2 id="android-app-development">Android app development</h2>
<p>Zunächst muss das SSL Zertifikat von oben zu den Resourcen der App
hinzugefügt werden:</p>
<pre><code>mkdir /path/to/android_project/app/src/main/res/raw/
cp ~/.mitmproxy/mitmproxy-ca-cert.pem /path/to/android_project/app/src/main/res/raw/mitm_proxy.pem</code></pre>
<p>Anschließend den folgenden Programmcode verwenden, um</p>
<ol type="1">
<li>den Proxy zu konfigurieren und</li>
<li>das SSL Zertifikat für die Anfrage zu setzen.</li>
</ol>
<div class="sourceCode" id="cb4"><pre
class="sourceCode java"><code class="sourceCode java"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">org</span><span class="op">.</span><span class="im">example</span><span class="op">.</span><span class="im">project</span><span class="op">.</span><span class="im">BuildConfig</span><span class="op">;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">InetSocketAddress</span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">Proxy</span><span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">URL</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">security</span><span class="op">.</span><span class="im">KeyStore</span><span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">security</span><span class="op">.</span><span class="im">cert</span><span class="op">.</span><span class="im">CertificateFactory</span><span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">HttpsURLConnection</span><span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">SSLContext</span><span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">TrustManagerFactory</span><span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="bu">URL</span> url <span class="op">=</span> <span class="kw">new</span> <span class="bu">URL</span><span class="op">(</span><span class="st">"https://example.org"</span><span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="bu">String</span> proxyHost <span class="op">=</span> <span class="st">"192.168.100.100"</span><span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> proxyPort <span class="op">=</span> <span class="dv">22222</span><span class="op">;</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a><span class="bu">HttpsURLConnection</span> connection <span class="op">=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="op">(</span>BuildConfig<span class="op">.</span><span class="fu">DEBUG</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a> <span class="bu">Proxy</span> proxy <span class="op">=</span> <span class="kw">new</span> <span class="bu">Proxy</span><span class="op">(</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a> <span class="bu">Proxy</span><span class="op">.</span><span class="fu">Type</span><span class="op">.</span><span class="fu">HTTP</span><span class="op">,</span> <span class="kw">new</span> <span class="bu">InetSocketAddress</span><span class="op">(</span>proxyHost<span class="op">,</span> proxyPort<span class="op">));</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a> connection <span class="op">=</span> <span class="op">(</span><span class="bu">HttpsURLConnection</span><span class="op">)</span> url<span class="op">.</span><span class="fu">openConnection</span><span class="op">(</span>proxy<span class="op">);</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a> <span class="bu">CertificateFactory</span> cf <span class="op">=</span> <span class="bu">CertificateFactory</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="st">"X.509"</span><span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a> <span class="bu">InputStream</span> caInput<span class="op">;</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create a KeyStore containing our trusted CAs</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a> <span class="bu">KeyStore</span> keyStore <span class="op">=</span> <span class="bu">KeyStore</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="bu">KeyStore</span><span class="op">.</span><span class="fu">getDefaultType</span><span class="op">());</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a> keyStore<span class="op">.</span><span class="fu">load</span><span class="op">(</span><span class="kw">null</span><span class="op">,</span> <span class="kw">null</span><span class="op">);</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a> <span class="co">// load mitm_proxy certificate</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a> caInput <span class="op">=</span> ApplicationInstance<span class="op">.</span><span class="fu">getContext</span><span class="op">().</span><span class="fu">getResources</span><span class="op">().</span><span class="fu">openRawResource</span><span class="op">(</span>R<span class="op">.</span><span class="fu">raw</span><span class="op">.</span><span class="fu">mitm_proxy</span><span class="op">);</span></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a> keyStore<span class="op">.</span><span class="fu">setCertificateEntry</span><span class="op">(</span><span class="st">"ca1"</span><span class="op">,</span> cf<span class="op">.</span><span class="fu">generateCertificate</span><span class="op">(</span>caInput<span class="op">));</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">finally</span> <span class="op">{</span></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a> caInput<span class="op">.</span><span class="fu">close</span><span class="op">();</span></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create a TrustManager that trusts the CAs in our KeyStore</span></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a> <span class="bu">String</span> tmfAlgorithm <span class="op">=</span> <span class="bu">TrustManagerFactory</span><span class="op">.</span><span class="fu">getDefaultAlgorithm</span><span class="op">();</span></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a> <span class="bu">TrustManagerFactory</span> tmf <span class="op">=</span> <span class="bu">TrustManagerFactory</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span>tmfAlgorithm<span class="op">);</span></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a> tmf<span class="op">.</span><span class="fu">init</span><span class="op">(</span>keyStore<span class="op">);</span></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create an SSLContext that uses our TrustManager</span></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a> <span class="bu">SSLContext</span> sslContext <span class="op">=</span> <span class="bu">SSLContext</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="st">"TLS"</span><span class="op">);</span></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a> sslContext<span class="op">.</span><span class="fu">init</span><span class="op">(</span><span class="kw">null</span><span class="op">,</span> tmf<span class="op">.</span><span class="fu">getTrustManagers</span><span class="op">(),</span> <span class="kw">null</span><span class="op">);</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a> connection<span class="op">.</span><span class="fu">setSSLSocketFactory</span><span class="op">(</span>sslContext<span class="op">.</span><span class="fu">getSocketFactory</span><span class="op">());</span></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">Exception</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a> <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a> <span class="bu">String</span><span class="op">.</span><span class="fu">format</span><span class="op">(</span><span class="st">"Cert exception: </span><span class="sc">%1$s</span><span class="st">"</span><span class="op">,</span> e<span class="op">.</span><span class="fu">getMessage</span><span class="op">()));</span></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a><span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a> connection <span class="op">=</span> <span class="op">(</span><span class="bu">HttpsURLConnection</span><span class="op">)</span> url<span class="op">.</span><span class="fu">openConnection</span><span class="op">();</span></span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Alternativ kann man den Proxy auch systemweit setzen. Dann muss man
das Zertifikat in den Android Einstellungen, Abschnitt Sicherheit
hinzufügen. Leider berücksichtigen nicht alle Apps die systemweite
Proxyeinstellung - z.B. Browser bringen gern ihre eigene Konfiguration
mit.</p>
<p>Wer nur die Anfragen seiner eigenen App analysieren möchte, sollte
daher den oben beschriebenen, direkten Weg einschlagen - das ist
zumindest meiner Erfahrung nach deutlich unkomplizierter.</p>
tag:www.eric-scheibler.de,2020-05-02:/de/blog/2020/05/kontoeroeffnung-online-identitaetsnachweis-im-jahr-2020/Kontoeröffnung - Online-Identitätsnachweis im Jahr 20202020-05-02T10:40:33Z2020-05-02T10:40:33Z<p>Vor kurzem habe ich ein Konto bei einer Bank eröffnet und dabei
unfreiwillig die Methoden zur Online-Identifikation ausprobiert. Im
Folgenden möchte ich meine Erfahrungen kurz zusammenfassen und dabei vor
allem auf Usability und Accessibility eingehen.</p>
<!-- more -->
<h2 id="inhaltsverzeichnis">Inhaltsverzeichnis</h2>
<ul>
<li><a href="#ausgangslage">1. Ausgangslage</a></li>
<li><a href="#videoident">2. VideoIdent</a></li>
<li><a href="#eid-des-personalausweises">3. eID des
Personalausweises</a></li>
<li><a href="#fazit">4. Fazit</a></li>
</ul>
<h2 id="ausgangslage">Ausgangslage</h2>
<p>Ich habe Mitte April 2020 ein Konto bei einer sog. Internetbank
eröffnet. Um diesen Prozess abschließen zu können, muss man sich in
Deutschland einer unpersönlichen <a
href="https://de.wikipedia.org/wiki/Legitimationspr%C3%BCfung">Legitimationsprüfung</a>
unterziehen.</p>
<p>Bisher hatte ich dafür das <a
href="https://de.wikipedia.org/wiki/Postident">PostIdent Verfahren</a>
eingesetzt - d.h. man geht mit dem Personalausweis zu einer Post und
bestätigt dort seine Identität. Das war zwar etwas unpraktisch, dafür
aber recht barrierearm, da der Postangestellte die meiste Arbeit für
mich getan hat.</p>
<p>Die Internetbank bot allerdings kein PostIdent in der Filiale an,
sondern ließ mir die Wahl zwischen VideoIdent und Ausweisen mittels eID
des Personalausweises. Wobei an dieser Stelle anzumerken ist, dass beide
Verfahren von mehreren Firmen angeboten werden. Die Post bietet neben
PostIdent in ihren Filialen beispielsweise auch VideoIdent und ausweisen
mittels eID an.</p>
<p>Die folgenden Abschnitte beschreiben daher vor allem die Umsetzung
der Verfahren durch die, von der Bank ausgewählten Dienstleister.
Einiges lässt sich allerdings durchaus auch verallgemeinern.</p>
<h2 id="videoident">VideoIdent</h2>
<p>Ich habe meinen Personalausweis vor 9 Jahren bekommen, die eID zur
Identifikation mangels Einsatzzweck allerdings nie ausprobiert. Daher
fiel meine Wahl auf VideoIdent. Man benötigt dafür lediglich ein
Smartphone oder ein Notebook bzw. einen PC mit einer Webcam.</p>
<p>Für das VideoIdent Verfahren wurde die <a
href="https://www.idnow.io">IDnow GmbH</a> beauftragt. Ich startete die
Legitimationsprüfung, wurde auf die Seite von IDnow weitergeleitet und
bekam dort den Link zu ihrer Android App und einen Transaktionscode
angezeigt. Nachdem ich die App installiert und den Code eingegeben habe,
wurde ein Video Chat aufgebaut und ich wartete auf einen freien
Mitarbeiter.</p>
<p>So weit, so gut. Was muss man nun tun?</p>
<p>Man muss sein Gesicht und den Personalausweis in die Kamera halten
und ein paar Angaben über sich laut aussprechen. Darunter fällt auch die
Personalausweisnummer und hier fingen die Probleme an.</p>
<p>Dem Gegenüber fiel natürlich sehr schnell auf, dass ich blind bin, da
ich nicht direkt in die Frontkamera geschaut habe. Daraufhin wurde mir,
noch bevor die Legitimationsprüfung so richtig starten konnte, gesagt,
dass ich nicht berechtigt sei, das VideoIdent Verfahren zu nutzen. Als
Grund wurde angeführt, dass man die Ausweisnummer mit den eigenen Augen
ab- und vorlesen können muss.</p>
<p>Ich hatte die Ausweisnummer wohl gemerkt jederzeit griffbereit und
hätte sie:</p>
<ol type="1">
<li>aus dem Gedächtnis rezitieren können</li>
<li>von einem Stück Papier ablesen können, auf das ich sie mir in
Blindenschrift notiert habe</li>
<li>von einer Braillezeile ablesen können, die an ein Notebook
angeschlossen ist</li>
<li>von einer Sprachausgabe vorlesen lassen und sie selbst wiederholen
können</li>
</ol>
<p>Auf meine vorgebrachten Lösungsvorschläge ging der Mitarbeiter von
IDnow jedoch gar nicht ein und beendete das Gespräch. Ich probierte es
dann noch einmal, bekam einen anderen Menschen zugelost, bekam aber die
gleiche Antwort. Das lässt darauf schließen, dass IDnow diese
Anforderungen vorgibt und der einzelne Mensch diese penibel umsetzen
muss, wenn er keinen Ärger von seinen Vorgesetzten bekommen möchte.</p>
<p>Ich beschwerte mich dann per E-Mail beim IDnow Support und bekam die
folgende Antwort:</p>
<blockquote>
<p>Sie können versichert sein, dass wir Ihr Feedback sehr ernst nehmen.
Unsere Mitarbeiter werden regelmäßig durch unser internes Trainerteam
geschult zudem führen wir Qualitätskontrollen der Gespräche durch. Sehr
gerne werden wir auch mit den Mitarbeitern ein Coaching bezüglich Ihres
Feedbacks durchführen.</p>
</blockquote>
<p>So weit, so üblich, wenn man Beschwerden dieser Art vorbringt. Was
genau geschult werden soll und ob sich an der Praxis der Diskriminierung
Blinder etwas ändern wird, kann man aus diesen Statement nicht ablesen.
Und selbst wenn: Wie überzeuge ich dann das Gegenüber im Video Chat von
dem Richtungswechsel?</p>
<p>In der Antwort des Supports fanden sich freundlicherweise auch noch
die Anforderungen der <a
href="https://de.wikipedia.org/wiki/Bafin">BaFin</a> an Blinde und
VideoIdent, denn die gibt es tatsächlich:</p>
<blockquote>
<p>Der User muss die Ausweisnummer selbst vorlesen oder aufs Band
sprechen. Eine zweite Person darf bei diesem Prozessschritt als
Hilfestellung agieren und die Ausweisnummer zur Wiederholung der zu
identifizierenden Person vorsagen. Der zu identifizierende User muss
diese dann wiederholen. Auch bei den Aufnahmen der Sicherheitsmerkmale
oder der Eingabe des Idents-Codes darf Hilfe durch eine weitere Person
angenommen werden</p>
</blockquote>
<p>Nirgendwo wird erwähnt, dass das Vorlesen ausschließlich durch die
eigenen Augen zu erfolgen hat. Die BaFin erlaubt hingegen sogar eine
Hilfsperson, die die Nummer vorspricht und der Blinde wiederholt.</p>
<p>Meine Methode 4 ist besonders nahe an den BaFin Ausnahmen, nur dass
die Hilfsperson kein Mensch sondern ein Computer ist. 1 und 2
funktionieren ganz ohne zusätzliche Technik. Alle vier Varianten
erfüllen meiner Meinung nach die BaFin Vorgaben.</p>
<p>Blindheit allein kann also nicht von der BaFin als
Ausschlusskriterium gemeint gewesen sein. Aber wenn die Firma nicht
möchte, dann möchte sie nicht. Da lässt sich kurz- bis mittelfristig
nichts ändern. Da ich die Kontoeröffnung aber ganz gern abschließen
wollte, entschied ich mich, das zweite angebotene Verfahren
auszuprobieren.</p>
<h2 id="eid-des-personalausweises">eID des Personalausweises</h2>
<p>Für die Legitimationsprüfung mittels Personalausweis beauftragte die
Bank die <a href="https://authada.de">AUTHADA GmbH</a>. Man benötigt
ebenfalls ein Smartphone und seinen Personalausweis mit freigeschalteter
<a
href="https://de.wikipedia.org/wiki/Personalausweis_%28Deutschland%29#eID-Funktion">eID</a>.</p>
<p>Ich hatte bereits damals bei der Abholung des Ausweises im Bürgerbüro
die eID freigeschaltet und die PIN gesetzt. Leider konnte ich mich an
den Vorgang nicht und an die PIN erst recht nicht mehr erinnern. Deshalb
musste ich erneut zum Bürgerbüro, um die PIN zurückzusetzen und eine
neue zu vergeben.</p>
<p>Dort stellte sich heraus, dass das Modul zur PIN-Eingabe
ausschließlich ein Touchscreen ist - ein selbstständiges Setzen der PIN
ist für Blinde also nicht möglich. Da wurde bei der Beschaffung wie
üblich nicht auf Barrierefreiheit geachtet. Immerhin besitzt das
EC-Kartenterminal, mit dem ich die anfallende Gebühr entrichten muss,
noch eine Tastatur - da frage ich mich schon lange, wann man auch dort
auf Touchscreens umstellt.</p>
<p>Nun konnte der eigentliche Legitimationsprüfungsvorgang starten. Dazu
wurde ich von der Bankwebseite auf die Webseite von AUTHADA
weitergeleitet. Dort folgte zunächst die Aufforderung, ihre App aus dem
Play Store zu laden. Bis dahin dachte ich noch, ich würde den weiteren
Vorgang mit der <a
href="https://de.wikipedia.org/wiki/AusweisApp2">Ausweisapp2</a>
durchführen, die zwar auch nicht barrierefrei aber wenigstens benutzbar
ist.</p>
<p>Statt dessen sollte ich mit der AUTHADA App nun einen QR Code von
meinem Notebookbildschirm scannen. Dieser Vorgang ist für Blinde zwar
nicht übermäßig komfortabel aber durchaus durchführbar. In der
Vergangenheit habe ich schon mehrere QR Codes gescannt und dafür max.
ein paar Sekunden benötigt.</p>
<p>Mit diesem QR Code war das allerdings nicht zu schaffen. Der Code bot
einen extrem schlechten Kontrast und ihm fehlte eine neutrale Umrandung.
Nach einigem probieren habe ich ihn heruntergeladen, in einem
Bildbetrachterprogramm geöffnet und reingezoomt - gleiches Ergebnis.
Auch Ausdrucken und vom Papier scannen funktionierte nicht.</p>
<p>An diesem Punkt kam ich nicht weiter und musste auf eine Assistenz
zurückgreifen. Das passiert mir im IT-Alltag sonst recht selten. Auch
die Assistenz brauchte mehrere Minuten und eine ruhige Hand, bis der
Code endlich von der App erkannt worden war. Hier könnte man durch die
Erzeugung eines kontrastreichen Codes inkl. Umrandung viel für die
Usability tun.</p>
<p>Anschließend folgte der NFC Scan des Ausweises und die PIN Eingabe.
Dazu hält man den Ausweis an die Geräterückseite des Smartphones und
gibt seine sechsstellige PIN ein. Das war der einzige Teil des
Verfahrens, der tatsächlich reibungslos funktioniert hat.</p>
<p>Als nächstes mussten Vorder- und Rückseite des Ausweises fotografiert
werden. Warum diese Fotos zusätzlich zur bereits erfolgten
elektronischen Verifikation benötigt werden, bleibt unklar. Vielleicht
verlangt die BaFin diese Fotos, vielleicht auch nicht. Es wird
jedenfalls nicht erläutert und die <a
href="https://authada.de/datenschutzerklaerung">Datenschutzerklärung</a>
ist so allgemein gehalten, dass sie beinahe zu jedem Unternehmen passen
könnte.</p>
<p>Unabhängig von der Datenschutzproblematik hätte ich spätestens hier
jemanden benötigt, der mir beim Anfertigen der Fotos hilft.</p>
<p>Nach dem erfolgreichem Foto-Upload generierte die App eine TAN, die
auf der zu Beginn geöffneten Webseite eingegeben werden muss. Es passte
zum bisherigen Gesamteindruck, dass diese TAN in einem Bild ohne
Bildbeschreibung daher kam und vom Screenreader deshalb nicht vorgelesen
werden konnte.</p>
<p>Damit war der Legitimationsprozess aber noch nicht abgeschlossen,
denn der “Absenden” Button neben dem TAN Eingabefeld lies sich nicht
klicken. Unklar. Schließlich erkannte meine Assistenz, dass man neben
dem App-aus-Play-Store-laden Hinweis noch einen Haken zur Bestätigung
setzen muss. Diese Checkbox war für den Screenreader gar nicht zu
erkennen und auch visuell offenbar nicht übermäßig markant.</p>
<p>Nachdem auch das geklärt war, konnte meine Assistenz den “Absenden”
Button anklicken und den Legitimationsprozess erfolgreich abschließen.
Auch der Button war mit der Tastatur nicht erreichbar aber das spielt
nach so vielen Problemen schon keine große Rolle mehr.</p>
<h2 id="fazit">Fazit</h2>
<p>Beide Verfahren waren nicht barrierefrei. Leider, so muss man sagen,
nicht mal ansatzweise.</p>
<p>IDnow schließt mich grundsätzlich von VideoIdent aus - da hätte nicht
einmal eine Assistenz etwas daran geändert. Ein anderer VideoIdent
Dienst mag das besser lösen aber diesbezüglich hat der Kunde nicht die
Wahl, sondern muss nehmen, was der, bei dem er sich identifizieren
möchte, ausgewählt hat.</p>
<p>Die AUTHADA eID Umsetzung scheitert gleich an mehreren Stellen.
Bedenkt man, dass sich das legitimieren mit dem Personalausweis
theoretisch an die gesamte Bevölkerung richtet, würde ich hier eine
Verpflichtung zu Usability und Accessibility erwarten. Das zöge im
Idealfall Nutzertests mit einem repräsentativen Teil der Bevölkerung
nach sich. Was am Ende schief gegangen ist, lässt sich von Außen nicht
nachvollziehen - es ist jedenfalls alles andere als praxistauglich und
komfortabel.</p>
<p>Dass das lokale Bürgerbüro Touchscreens zur PIN-Verwaltung eingekauft
hat, ist dann schon beinahe folgerichtig.</p>
<p>Meine Legitimationspräferenzen sehen also wie folgt aus:</p>
<ol type="1">
<li>PostIdent in der Filiale: Der Weg zur Post ist für mich relativ kurz
und problemlos zurückzulegen. Alles weitere erledigt die Post.</li>
<li>VideoIdent: Lässt sich von zu Hause mit geringen
Hardwareanforderungen durchführen und wäre noch einen Versuch wert,
solange das Unternehmen nicht IDnow heißt.</li>
<li>eID: Zumindest die Umsetzung von AUTHADA ist eine absolute Notlösung
und nur mit Assistenz durchzuführen. In meinem Fall war es tatsächlich
die einzige Möglichkeit, sich zu legitimieren. Ich würde sie daher
zumindest nicht komplett ausschließen. Vielleicht haben andere Anbieter
sich bei Usability und Accessibility auch mehr Mühe gegeben.</li>
</ol>
<p>Von der Bank würde ich mir wünschen, dass sie ein möglichst breites
Spektrum an Legitimationsverfahren anbietet und im Idealfall auch
Barrierefreiheit ein Kriterium für die Anbieterauswahl darstellt. Statt
dessen war die Antwort dort ein Schulterzucken.</p>
<p>Ich mache mir bezüglich der Online-Funktionen des Personalausweises
für die Zukunft auch kaum Hoffnung auf Besserung. Schon einen einzelnen
Anbieter zur Barrierefreiheit zu verpflichten, erscheint unrealistisch
aber mehrere? Wer sollte das kontrollieren? Was passiert bei
Verstößen?</p>
<p>Da bleibt nur der Trost, dass man diese Funktionen nicht allzu häufig
in Anspruch nehmen muss.</p>
tag:www.eric-scheibler.de,2018-12-29:/de/blog/2018/12/mpd-aktuellen-titel-ansagen/MPD - Aktuellen Titel ansagen2018-12-29T02:14:13Z2018-12-29T02:14:13Z<p>Auf meinem Server läuft ein <a
href="https://www.musicpd.org">MPD</a>, welcher für die Musikwiedergabe
zuständig ist. Das folgende Skript für den Mac sagt den aktuellen Titel
nach jedem Titelwechsel an. Es verwendet die bei Mac OS vorinstallierte
TTS und versucht, die Sprache des Songtitels zu ermitteln.</p>
<!-- more -->
<p>Download: <a
href="/downloads/mpd_speak_current_song.py">mpd_speak_current_song.py</a></p>
<pre><code>#!/Users/eric/.virtualenvs/speak_current_song/bin/python
# -*- coding: utf-8 -*-
# mpd: speak currently playing song after it has changed
#
# This script uses the mpd client mpc to watch for song changes on the running mpd server and speaks them aloud
# Song title language detection with https://github.com/Mimino666/langdetect
# It's written for Mac OS but could be easily adapted to Linux as well (change tts_command variable)
#
# author: Eric Scheibler <email@eric-scheibler.de>
# Date: 2019-03-29
# Version: 0.2
#
# Installation requirements:
# brew install mpc
# pip install langdetect
#
# Usage: Must run in background for example within a screen session
# ./mpd_speak_current_song
# configuration
#
# adapt the following params to your needs
# leave them blank if not required
#
# mpd host and port
mpd_host = "192.168.2.11"
mpd_port = "6600"
# audio output device
# Mac: get all devices with: say -a '?'
audio_output_device = ""
# voice table
# Mac: get all installed voices with: say -v '?'
voices = {
"de" : {"name":"Yannick", "rate":280},
"en" : {"name":"Samantha", "rate":200},
"fr" : {"name":"Thomas", "rate":200},
# default: different German voice
"default" : {"name":"Anna", "rate":240}
}
import subprocess, sys, threading
from langdetect import detect, DetectorFactory
from langdetect.lang_detect_exception import LangDetectException
# construct commands
#
# control mpd with: mpc ...
mpc_command = ["mpc", "--quiet"]
if mpd_host:
mpc_command += ["--host", mpd_host]
if mpd_port:
mpc_command += ["--port", mpd_port]
# speak with: say ...
tts_command = ["say"]
if audio_output_device:
tts_command += ["-a", audio_output_device]
class SpeakCurrentSong(threading.Thread):
def __init__(self, song, voice):
threading.Thread.__init__(self)
self.song = song
self.voice = voice
# class variables
self.process = None
self.finished = False
self.killed = False
def run(self):
# pause playback
if not self.killed:
self.execute(mpc_command + ["pause"])
# announce new song
if not self.killed:
self.execute(tts_command + ["-v", self.voice.get("name"), "-r", str(self.voice.get("rate")), self.song])
# resume playback
if not self.killed:
self.execute(mpc_command + ["play"])
self.finished = True
def execute(self, command):
self.process = subprocess.Popen(command)
self.process.wait()
def kill(self):
self.process.kill()
self.killed = True
def main():
speakCurrentSongThread = None
# force deterministic language detection results
DetectorFactory.seed = 0
while True:
try:
# wait for change of current song
current_song = subprocess.check_output(mpc_command + ["current", "--wait"], encoding='UTF-8').strip()
except subprocess.CalledProcessError as e:
# could not get current song
error_message = "Error: Could not get the current song"
subprocess.Popen(tts_command + ["-v", "Alex", error_message])
print('{}: {}'.format(error_message, e))
sys.exit(1)
else:
# detect language and get tts voice
voice = voices.get("default")
try:
language = detect(current_song)
except LangDetectException as e:
pass
else:
if language in voices.keys():
voice = voices.get(language)
# start SpeakCurrentSong thread
if speakCurrentSongThread:
# kill old thread if necessary
if not speakCurrentSongThread.finished:
speakCurrentSongThread.kill()
speakCurrentSongThread.join()
speakCurrentSongThread = SpeakCurrentSong(current_song, voice)
speakCurrentSongThread.start()
# log to stdout
print('{}.{}: {}'.format(language, voice.get("name"), current_song))
if __name__ == "__main__":
main()
</code></pre>
tag:www.eric-scheibler.de,2018-02-10:/de/blog/2018/02/clipboardtospeech-android-app-liest-den-inhalt-der-zwischenablage-vor/ClipboardToSpeech - Android App liest den Inhalt der Zwischenablage vor2018-02-10T09:43:24Z2018-02-10T09:43:24Z<p>Diese Android App überwacht die Zwischenablage und liest neu
hinzugefügten Text automatisch vor.</p>
<!-- more -->
<p>Weitere Features sind:</p>
<ul>
<li>Verwendet das Standardsprachausgabemodul</li>
<li>Beinhaltet eine History der letzten Einträge der Zwischenablage</li>
<li>Startet automatisch nach dem Booten des Geräts</li>
<li>Erstellt eine Low-Priority Notification in der
Benachrichtigungsleiste wenn der Hintergrunddienst aktiv ist</li>
<li>Benötigt mindestens Android Version 4.1 und ist kompatibel zu 8.0
und 8.1</li>
</ul>
<p>Git Repository klonen:</p>
<pre><code>git clone https://github.com/scheibler/ClipboardToSpeech/</code></pre>
<p>Die bereits kompilierte Programmversion kann wie folgt installiert
werden:</p>
<pre><code>adb install app/src/main/misc/releases/ClipboardToSpeech_vx.x.x.apk</code></pre>
<p>Alternativ von Hand bauen:</p>
<pre><code>./gradlew build</code></pre>
tag:www.eric-scheibler.de,2016-07-03:/de/blog/2016/07/laptop-bildschirm-unter-linux-abschalten/Laptop Bildschirm unter Linux abschalten2016-07-03T20:29:42Z2016-07-03T20:29:42Z<p>Dieser Artikel beschreibt, wie der Laptop Bildschirm von Lenovo
Thinkpads unter Linux dauerhaft abgeschaltet werden kann. Dies spart im
Akkubetrieb nicht nur eine Menge Strom sondern schützt auch die
Privatsphäre. Benötigt root Rechte. Getestet mit einem Thinkpad T450
unter Debian Stretch.</p>
<!-- more -->
<p>Das folgende Skript unter /usr/local/sbin/monitor abspeichern:</p>
<pre><code>#!/bin/bash
if [ -f /tmp/screen_stays_active ]; then
echo 0 > /sys/class/backlight/intel_backlight/brightness
rm /tmp/screen_stays_active
echo "Disable screen"
else
echo 850 > /sys/class/backlight/intel_backlight/brightness
touch /tmp/screen_stays_active
echo "Restore screen brightness"
fi</code></pre>
<p>und ausführbar machen:</p>
<pre><code>chmod +x /usr/local/sbin/monitor</code></pre>
<p>Da sich der Monitor in unregelmäßigen Zeitabständen wieder
einschaltet, muss die Helligkeit alle 20 Sekunden auf 0 gesetzt werden.
Ich habe dies mit drei Cronjobs des root Users gelöst. Ein weiteres
Skript mit einer endlosen While-Loop wäre auch denkbar.</p>
<pre><code># crontab -e
* * * * * if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi
* * * * * sleep 20 && if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi
* * * * * sleep 40 && if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi</code></pre>
<p>Es ist zu beachten, dass der Bildschirm nach der Installation der
Crontab sofort komplett abgeschaltet wird. Dies gilt natürlich auch noch
nach einem Neustart des Systems.</p>
<p>Der Bildschirm lässt sich anschließend mittels
<code>sudo monitor</code> ein- und auch wieder abschalten. Wer dafür
nicht jedes Mal ein Passwort eingeben möchte, kann in der sudoers Datei
eine Ausnahme definieren. Dazu den <code>visudo</code> Befehl als root
ausführen und die folgende Zeile anhängen:</p>
<pre><code>MY_USER_NAME ALL = NOPASSWD: /usr/local/sbin/monitor</code></pre>
<p>Bei der Gelegenheit lässt sich auch gleich unter root noch ein
Cronjob anlegen, der die Tastaturbeleuchtung im Minutentakt
deaktiviert:</p>
<pre><code># crontab -e
* * * * * echo 0 > /sys/class/leds/tpacpi::kbd_backlight/brightness</code></pre>
<p>Weitere Informationen gibt es unter <a
href="http://www.thinkwiki.org/wiki/ThinkLight"
class="uri">http://www.thinkwiki.org/wiki/ThinkLight</a></p>
tag:www.eric-scheibler.de,2016-05-06:/de/blog/2016/05/android-app-tactileclock-vibriert-die-aktuelle-uhrzeit/Android App Tactile Clock vibriert die aktuelle Uhrzeit2016-05-06T21:45:20Z2016-05-06T21:45:20Z<p>Ich habe eine kleine Android App erstellt, welche die aktuelle
Uhrzeit als Vibrationspattern ausgibt. Dazu muss das Display
abgeschaltet sein und der Power-Button zweimal kurz hintereinander
betätigt werden (Intervall von 500 bis 1350 Millisekunden).</p>
<p>Update 2024: Version 2.2.0 unterstützt Android 14</p>
<!-- more -->
<p>Das Programm startet automatisch nach dem Booten des Geräts. Es läuft
auf allen Android Geräten ab Version 4.0 und ist unter der GPL V3
lizenziert.</p>
<p>Grundsätzlich wird zwischen den folgenden zwei Vibrationspattern
unterschieden: Eine kurze Vibration steht für die Ziffer 1 und eine
lange Vibration für die Ziffer 5. Die Ziffer 2 wird somit durch zwei
aufeinander folgende kurze Vibrationen, die 6 durch eine lange und eine
kurze Vibration dargestellt usw.. Die 0 bildet mit zwei langen
Vibrationen eine Ausnahme.</p>
<p>Beispiele:</p>
<pre><code>01:16 = .. k ... k .. l . k
02:51 = .. k . k ... l .. k
10:11 = k .. l . l ... k .. k</code></pre>
<p>Erläuterung:</p>
<p>Die Uhrzeit wird Ziffer für Ziffer verarbeitet. k = kurz, l = lang.
Bei der Stundenangabe werden führende Nullen weggelassen. Um die
Vibrationen unterscheiden zu können, existieren drei unterschiedlich
lange Pausen, gekennzeichnet durch die Anzahl von Punkten in den obigen
Beispielen. Ein einzelner Punkt steht für die Pause zwischen zwei
Vibrationen, zwei Punkte symbolisieren die Trennung zwischen den Ziffern
im Stunden- und Minutenfeld und drei Punkte trennen die Stunden von den
Minuten.</p>
<p>Download und Quellcode:</p>
<ul>
<li><a
href="https://play.google.com/store/apps/details?id=de.eric_scheibler.tactileclock&hl=de">Google
Play Store</a></li>
<li><a
href="https://apt.izzysoft.de/packages/de.eric_scheibler.tactileclock">IzzySoft
Android Repository</a></li>
<li>APK Download: <a
href="/downloads/TactileClock_v2.2.0.apk">TactileClock_v2.2.0.apk</a>
(aktualisiert am 25.03.2024)</li>
<li><a href="https://github.com/scheibler/TactileClock">Quellcode auf
GitHub</a></li>
<li><a
href="https://www.eric-scheibler.de/de/tactile_clock_datenschutz/">Datenschutzinformationen</a></li>
</ul>