commit
55fd598405
22 changed files with 1474 additions and 0 deletions
@ -0,0 +1,3 @@ |
||||
[submodule "harbor"] |
||||
path = themes/harbor |
||||
url = https://github.com/matsuyoshi30/harbor |
@ -0,0 +1,6 @@ |
||||
--- |
||||
title: "{{ replace .Name "-" " " | title }}" |
||||
date: {{ .Date }} |
||||
draft: true |
||||
--- |
||||
|
@ -0,0 +1,45 @@ |
||||
baseURL = "http://example.org/" |
||||
languageCode = "en-us" |
||||
title = "wanderings" |
||||
theme = "harbor" |
||||
|
||||
[Author] |
||||
name = "la Fleur" |
||||
|
||||
[outputs] |
||||
section = ["JSON", "HTML"] |
||||
|
||||
[[params.nav]] |
||||
identifier = "about" |
||||
name = "About" |
||||
icon = "fas fa-user fa-lg" |
||||
url = "/about/" |
||||
weight = 3 |
||||
|
||||
[[params.nav]] |
||||
identifier = "tags" |
||||
name = "Tags" |
||||
icon = "fas fa-tag fa-lg" |
||||
url = "tags" |
||||
weight = 3 |
||||
|
||||
[[params.nav]] |
||||
identifier = "categories" |
||||
name = "Category" |
||||
icon = "fas fa-folder-open fa-lg" |
||||
url = "categories" |
||||
weight = 3 |
||||
|
||||
[[params.nav]] |
||||
identifier = "search" |
||||
name = "Search" |
||||
icon = "fas fa-search fa-lg" |
||||
url = "search" |
||||
weight = 3 |
||||
|
||||
[params.logo] |
||||
url = "icon.jpg" # static/images/icon.jpg |
||||
width = 50 |
||||
height = 50 |
||||
alt = "Logo" |
||||
|
@ -0,0 +1,6 @@ |
||||
--- |
||||
title: About |
||||
permalink: /about/ |
||||
--- |
||||
I am la Fleur, login `lafleur`. I'm a free software enthousiast. Else about me, |
||||
you will discover by scrolling this site. |
@ -0,0 +1,181 @@ |
||||
+++ |
||||
title = "Arch on a Raspberry Pi - the basics" |
||||
date = 2020-09-26 |
||||
tags = ["admin"] |
||||
toc = true |
||||
+++ |
||||
Installing Arch on a Raspberry Pi is amazingly easy. the Arch ARM project gives |
||||
instructions for the basic installation [here][aw-arm-install]. |
||||
|
||||
## Chroot into the SD card |
||||
|
||||
When you finished installing the basic system to the SD card, you may wish to |
||||
install the following packages in your host system (we'll suppose your host |
||||
system is already an Arch linux) : `arch-install-script` that provides the |
||||
`arch-chroot` command, and `qemu-user-static-bin`, that provides the means to |
||||
run ARM binaries from the host system. Then you can mount the SD card on e.g. |
||||
`/mnt` and do : |
||||
``` |
||||
# arch-chroot /mnt |
||||
``` |
||||
`arch-chroot` will mount all useful mounts like `/mnt/proc`, `/mnt/sys`, |
||||
`/mnt/dev`, and event `/mnt/boot` if it's in `/mnt/etc/fstab`. When you're done, |
||||
it will clean everything and hopefully let you unmount the drive cleanly. |
||||
|
||||
## Setup SSH |
||||
|
||||
Over there you can modify your target system, which provides a user named `pi`. |
||||
You might use that or add a user. Make sure you set up ssh login credentials. If |
||||
in doubt check [Archlinux wiki's OpenSSh page][aw-ssh] ; I believe it's |
||||
installed by default on archlinuxarm. |
||||
|
||||
When you're done just hit `Ctrl-d` to unbind the chroot. |
||||
|
||||
## Networking |
||||
|
||||
The tricky bit is to be able to log into it on a headless config. You will have |
||||
to make sure it's reachable on the network so you can ssh into it. There are a |
||||
few options here : if the RPi provides an ethernet plug, connect over ethernet. |
||||
If not (as for the RPi zero series), connect with USB over ethernet. If you need |
||||
(or crave) to connect wirelessly, the wifi leaves you options to either make it |
||||
connect to your local network, or turn the RPi into a wifi hotspot. |
||||
|
||||
Note that wireless networking does imply a certain overhead ; you might consider |
||||
turning it off when in production use, specially when running JACK (I read it |
||||
somewhere in a JACK tutorial page but I can't remember where). |
||||
|
||||
### Wired over USB |
||||
|
||||
Skip this part if your RPi has an ethernet socket. Else chroot in the SD card. |
||||
Add the following to `/boot/cmdline.txt` : |
||||
|
||||
modules_load=dwc2,g_ether |
||||
|
||||
Now into `/boot/config.txt`, add the following line : |
||||
|
||||
dtoverlay=dwc2 |
||||
|
||||
This will let the system load an ethernet over USB module at boot-time. To have |
||||
it setup at boot-time run : |
||||
``` |
||||
# systemctl enable systemd-networkd |
||||
``` |
||||
And create or edit `/etc/systemd/network/01-usb.network` so that it reads : |
||||
``` systemd |
||||
[Match] |
||||
Name=usb* |
||||
|
||||
[Network] |
||||
LinkLocalAddressing=ipv4 |
||||
IPv4LLRoute=yes |
||||
``` |
||||
get out of the chroot, unmount the SD card and boot. |
||||
|
||||
### Wireless - connect to a local network |
||||
|
||||
You will need the `wpa_supplicant` package, and a systemd service to launch it. |
||||
Chroot into the SD card, and run as root : |
||||
``` |
||||
# pacman -S wpa_supplicant |
||||
``` |
||||
`wpa_supplicant` can be started by `systemd-networkd` : edit/create |
||||
`/etc/systemd/network/05-wlan.network` with the following contents : |
||||
``` systemd |
||||
[Match] |
||||
Name=wlan* |
||||
|
||||
# Run as a client with wpa_supplicant |
||||
[Network] |
||||
DHCP=ipv4 |
||||
MulticastDNS=yes |
||||
``` |
||||
Now edit or create `/etc/wpa_supplicant/wpa_supplicant-wlan0.conf`, and add your |
||||
local network credentials. Refer to `man wpa_supplicant.conf` for details. The |
||||
syntax is : |
||||
``` wpa-supplicant |
||||
network={ |
||||
ssid="THE_NAME_OF_YOUR_ACCESS_POINT" |
||||
psk="THE_ACCESS_POINT_PASSWORD" |
||||
} |
||||
``` |
||||
`systemd-networkd` will start `wpa_supplicant@wlan0.service`, who will in turn |
||||
use the latter config file to run. You can quit the chroot, unmount the SD card |
||||
and boot it ; the system should connect to the local network at boot-time. You |
||||
can add Access Points at run-time with the `wpa_passphrase` utility, like in : |
||||
``` |
||||
wpa_passphrase AP_NAME AP_PASSWORD | sudo tee --append |
||||
/etc/wpa_supplicant/wpa_supplicant-wlan0.conf |
||||
``` |
||||
|
||||
### Wireless - fallback to hotspot |
||||
|
||||
It can be very handy to fall back to setting up an Access Point if we can't |
||||
connect to any known network. This way you're still able to `ssh` back into the |
||||
RPi and for instance register a new Access Point to connect to. This trick was |
||||
derived from [this post][replace-hostapd]. |
||||
|
||||
`wpa_supplicant` provides this feature with a rather ... cryptic manner ; see by |
||||
yourself : |
||||
``` |
||||
# AP configuration as a fallback : |
||||
network={ |
||||
ssid="RPi" |
||||
mode=2 |
||||
priority=-999 |
||||
key_mgmt=WPA-PSK |
||||
psk="a rather long raspberry pi password" |
||||
frequency=2412 |
||||
} |
||||
``` |
||||
You just insert this snippet in `/etc/wpa_supplicant/wpa_supplicant-wlan0.conf` |
||||
so that it gets loaded only if the other networks are not found. It doesn't have |
||||
to sit in the bottom, so you can still append more attractive APs with |
||||
`wpa_passphrase`. Even if you don't use the `priority` option, `wpa_supplicant` |
||||
will be smart enough to load it only if no known host is found. The RPi Zero W |
||||
doesn't do 802.11g, it's only capable of 802.11b, but you don't need to tell it, |
||||
it will handle that. I actually tested all these options, because I couldn't |
||||
find documentation on these features. |
||||
|
||||
But it won't give you or your client an IP address - for this a DHCP server is |
||||
needed. The RPi can act like one with the `dnsmasq` package, who will in turn |
||||
require the interface to be setup with a static address. As stated in this |
||||
[StackExchange discussion][se-static-addr], you can have several addresses on |
||||
the same interface at the same moment. So in |
||||
`/etc/systemd/network/05-wlan0.network`, append this line to the `[Network]` |
||||
section : |
||||
``` |
||||
Address=192.168.2.1/24 |
||||
``` |
||||
We just want `dnsmasq` to run if the RPi is in Access Point mode. Systemd can |
||||
help here, you may add a conditional on `dnsmasq.service`'s execution with the |
||||
following snippet : |
||||
|
||||
file: /etc/systemd/system/dnsmasq.service.d/only-if-we-are-an-AP.conf |
||||
|
||||
``` systemd |
||||
# Insert a test that fails if the device is not an Access Point. |
||||
[Unit] |
||||
After=wpa_supplicant@wlan0.service |
||||
|
||||
[Service] |
||||
ExecStartPre=/bin/sh -c 'eval $(wpa_cli -i wlan0 status) && test "$mode" == "AP"' |
||||
|
||||
[Install] |
||||
WantedBy=wpa_supplicant@wlan0.service |
||||
``` |
||||
If you want to know what it does, try `sudo wpa_cli -i wlan0 status` ; you'll |
||||
see it outputs various informations about the network device, including its |
||||
mode, with possible values `AP` and `managed`. The above test succeeds only if |
||||
the interface is configured as an Access Point. And Systemd services fail if any |
||||
of the ExecStartPre instructions fail. The drawback of this setup is that the |
||||
service will simply appear as failed in journalctl, with no explanation. |
||||
|
||||
Copy the snippet to the given location, reboot. If you can't ping the RPi on the |
||||
local network, look for it in the available APs. Connect to it, ssh it. You're |
||||
done ! |
||||
|
||||
|
||||
[aw-arm-install]: https://archlinuxarm.org/platforms/armv6/raspberry-pi |
||||
[aw-ssh]: https://wiki.archlinux.org/index.php/OpenSSH |
||||
[replace-hostapd]: https://raspberrypi.stackexchange.com/questions/94970/replacing-hostapd-with-wpa-supplicant |
||||
[se-static-addr]: https://unix.stackexchange.com/questions/308944/systemd-networkd-default-static-address-if-dhcp-fails |
@ -0,0 +1,62 @@ |
||||
+++ |
||||
title = "Local Name Resolution in Arch" |
||||
date = 2020-09-26 |
||||
tags = ["admin"] |
||||
+++ |
||||
It's quite handy to be able to access clients connected at the same Access Point |
||||
as you using their hostname. There are a few technologies to do that : mostly, |
||||
Apple's Bonjour translates as mDNS in the Linux world, then there is Microsoft's |
||||
NetBIOS protocol, that is provided by samba under Linux but not running as a |
||||
default. And Microsoft also created the LLMNR resolution protocol, that ships |
||||
with Windows starting at Vista (and with 7, 8 and 10). |
||||
|
||||
To have local name resolution, your best option is probably `systemd-resolved`, |
||||
since it does both mDNS and LLMNR. On the other side, avahi does mDNS and lets |
||||
you advertise specific services on the network. If you're interested in this you |
||||
might want to go for it ; check [Archwiki's page on network |
||||
configuration][aw-net-conf]. Here we will deal with `systemd-resolved`. |
||||
|
||||
The first thing to do is to disable avahi services, since they interfere with |
||||
`systemd-networkd`. Go on and |
||||
``` |
||||
# systemctl disable avahi-daemon |
||||
``` |
||||
Now we need to check how you do domain resolution on your computer. Under Linux |
||||
this is dependent on who manages `/etc/resolv.conf`. If it's a symlink to some |
||||
NetworkManager folder, it's NetworkManager. If it's linked to some Systemd |
||||
folder, then it's `systemd-resolved`. If it is NetworkManager and you want to |
||||
switch to `systemd-resolved` and keep NetworkManager to manage connections, just |
||||
do : |
||||
``` |
||||
# ln -sf /run/systemd/resolve/stub-resolve.conf /etc/resolv.conf |
||||
``` |
||||
It may also be a real file, meaning your computer uses static IP addresses for |
||||
DNS servers. In this case you can do the same (back it up if you will). |
||||
|
||||
Edit `/etc/systemd/resolved.conf` so that inside of the `[Resolve]` section, you |
||||
have `MulticastDNS=yes` and `LLMNR=yes`. After that, you will have to |
||||
``` |
||||
# systemctl enable systemd-resolved |
||||
``` |
||||
And reboot. |
||||
|
||||
Now run `resolvectl` to see how it goes. If your network interface doesn't |
||||
specifically allow MulticastDNS, you will have to tell the network manager to do |
||||
it. If it's `systemd-networkd`, you will need to add the very same option in the |
||||
relevant `.network` files in `/etc/systemd/network/`. If it's NetworkManager, no |
||||
luck, you will have to update each and every connection by adding `mdns=2` to |
||||
the `[connection]` section of the connection definition in |
||||
`/etc/NetworkManager/system-connections`. |
||||
|
||||
If you changed anything, you might want to reboot again. Then try |
||||
``` |
||||
$ resolvectl query SOMEHOSTNAME.local |
||||
``` |
||||
SOMEHOSTNAME being the name of a device you know is connected. It should answer |
||||
through mDNS. You may also try `resolvectl query SOMEHOSTNAME`, skipping the |
||||
`.local` extension ; it should answer with LLMNR. Do note that Android devices |
||||
don't seem to show up on the local network. But they do see declared neighbours |
||||
; I suspect they ship with LLMNR since they're only able to `ping` names without |
||||
the `.local` extension. |
||||
|
||||
[aw-net-conf]: https://wiki.archlinux.org/index.php/Network_configuration#Local_network_hostname_resolution |
@ -0,0 +1,137 @@ |
||||
+++ |
||||
title = "Arch on a Raspberry Pi - sound and MIDI" |
||||
date = 2020-09-26 |
||||
tags = ["admin"] |
||||
toc = true |
||||
+++ |
||||
This post follows a first one on [basics of Arch onto a Raspberry |
||||
Pi][arch-basics]. Now we will see how to have high quality, low latency sound |
||||
and a MIDI keyboard on this beautiful but still useless system. |
||||
|
||||
## MIDI keyboard with JACK |
||||
|
||||
To reliably output sound using a MIDI keyboard and have a reasonable latency, |
||||
consider dropping pulseaudio, installing and setting up JACK, and setting up |
||||
fluidsynth to use JACK. |
||||
|
||||
### Disable pulseaudio's autostart with systemd |
||||
Run as the relevant user : |
||||
|
||||
$ systemctl --user mask pulseaudio.socket |
||||
|
||||
### Setup JACK |
||||
|
||||
First install the `jack2` package, and enable it at login with : |
||||
|
||||
$ systemctl --user enable jack@lowlatency.service |
||||
|
||||
The `@` argument sets a JACK configuration file that will be searched in the |
||||
following places, first match wins : |
||||
|
||||
~/.config/jack/lowlatency.conf |
||||
/etc/jack/lowlatency.conf |
||||
|
||||
If you use a DAC, drop a second conf file in `~/.config/jack/MYDAC.conf`. For |
||||
instance HifiBerry-style DACs are identified as device `hw:sndrpihifiberry`. |
||||
In this case create `~/.config/jack/hifiberry.conf` as follows, and instead of |
||||
the former, enable `jack@hifiberry.service` : |
||||
|
||||
# The name of the JACK server |
||||
JACK_DEFAULT_SERVER="default" |
||||
# Options to JACK (e.g. -m, -n, -p, -r, -P, -t, -C, -u, -v) |
||||
JACK_OPTIONS="" |
||||
|
||||
# Audio backend (e.g. alsa, dummy, firewire, netone, oss, portaudio) |
||||
DRIVER="alsa" |
||||
# Device name (used by the audio backend) - defaults to "hw:0" |
||||
DEVICE="hw:sndrpihifiberry" |
||||
|
||||
# Specific settings for the audio backend in use |
||||
DRIVER_SETTINGS="-n2 -p128 -r48000" |
||||
|
||||
Note that `JACK_OPTIONS` is empty ; all the useful options go in |
||||
`DEVICE_SETTINGS`. I suppose it's a bug of the JACK service file. |
||||
|
||||
More about DACs at [the end of the post](#use-an-external-sound-card). |
||||
|
||||
### Setup fluidsynth |
||||
|
||||
Install the `fluidsynth` package ; it comes with a default user service that |
||||
will connect to the JACK service if you tell it to in its conf file |
||||
`~/.config/fluidsynth`. First do : |
||||
``` |
||||
$ systemctl --user enable fluidsynth |
||||
``` |
||||
You will need a soundfont in `/usr/share/soundfonts/default.sf2`. I actually |
||||
made it a symbolical link to a soundfont file in my home dir. If you're |
||||
desperate about soundfonts install the `soundfont-fluid` package. |
||||
|
||||
JACK2 is said to need package `a2jmidid` to detect MIDI keyboards. I've noticed |
||||
that in fact `fluidsynth` was very capable of detecting them on its own with the |
||||
option `-o midi.autoconnect=1`, be it at boot or hotplugged. |
||||
|
||||
A useful feature is that it can read a command file with the -f option ; an |
||||
arguably preferable strategy is to create and load one in |
||||
|
||||
/usr/share/soundfonts/fluidsynth.command |
||||
|
||||
Finally my `~/.config/fluidsynth` reads as follows : |
||||
|
||||
# Mandatory parameters (uncomment and edit) |
||||
SOUND_FONT=/usr/share/soundfonts/default.sf2 |
||||
|
||||
# Additional optional parameters (may be useful, see 'man fluidsynth' for further info) |
||||
# -C0 disables chorus |
||||
# -R0 disables reverb |
||||
# -g0.2 would be the default gain |
||||
# -m can be jack, alsa, alsa_seq or raw ? jack should be more efficient |
||||
# -K number of MIDI channels (minimum 16) |
||||
# -f command file input |
||||
OTHER_OPTS="-a jack -j -f /usr/share/soundfonts/fluidsynth.command -o midi.autoconnect=1 -o synth.polyphony=64" |
||||
|
||||
Its systemd service's default target used to be "multi-user" whereas it should |
||||
have been "default" (bug resolved as of version 2.1.5-2). One can now enable it |
||||
with |
||||
On older versions of the program, you had to do instead : `systemctl --user |
||||
add-wants default fluidsynth` |
||||
|
||||
## Autostart |
||||
|
||||
If you want the systemd user services started at boot do _as root_ : |
||||
``` |
||||
# loginctl enable-linger YOURUSERNAME |
||||
``` |
||||
|
||||
## Deal with latency |
||||
|
||||
To optimise latency, you will want to use a realtime process for jackd. |
||||
|
||||
You can follow [instructions for Arch][aw-instr] to have the right to make your |
||||
process prioritary on system tasks. Eventually, you should belong to the |
||||
`realtime` group. After that jackd should detect it's able to run a realtime |
||||
process and proceed. You can check that it does with |
||||
``` |
||||
journalctl --user -u jack@hifiberry -e |
||||
``` |
||||
(replace `hifiberry` with the config name you gave at the beginning of this |
||||
post). The `-e` option jumps at the end of the logs. |
||||
|
||||
I was only able to use the alsa-seq MIDI plugin with JACK, but some people |
||||
report more effectiveness with jack's own plugin. You can check [Archwiki's |
||||
JACK page][aw-jack-midi] for detailed instructions. |
||||
|
||||
Another [Archwiki page on professional audio][aw-pro-audio] mentions a |
||||
`-Xalsarawmidi` option in JACK used with a2jmidid to enhance MIDI jitter. |
||||
|
||||
## Use an external sound card |
||||
|
||||
For Phat DACs, follow these [instructions][dac-instr]. What's a Phat ? It's a |
||||
small cardboard that fits above a Raspberry Pi, plugged into its IO pins. And a |
||||
DAC ? Digital to Analog Converter - in other words, an audio card. |
||||
|
||||
|
||||
[arch-basics]: {% post_url computing/2020-09-26-Arch-on-RPi %} |
||||
[aw-instr]: https://wiki.archlinux.org/index.php/Realtime_process_management#PAM-enabled_login |
||||
[aw-jack-midi]: https://wiki.archlinux.org/index.php/JACK_Audio_Connection_Kit#MIDI |
||||
[aw-pro-audio]: https://wiki.archlinux.org/index.php/Professional_audio#MIDI |
||||
[dac-instr]: https://www.hifiberry.com/docs/software/configuring-linux-3-18-x/ |
@ -0,0 +1,55 @@ |
||||
+++ |
||||
title = "disks backup" |
||||
date = 2020-09-16 |
||||
tags = ["admin"] |
||||
+++ |
||||
Backing up a whole disk is always quite sensible ; you rarely have the occasion |
||||
to test if your backup really works. Besides, the logical architecture of a disk |
||||
and its partitions can be quite sophisticated, with numerous different options, |
||||
that vary much between operating systems. Here we will describe how to backup |
||||
and restore a disk with some primary partitions. We will assume it is a bootable |
||||
disk with a Master Boot Record at the very beginning of it (MBR is limited to |
||||
446 bytes by conception). Note that most recent machines boot in UEFI mode, |
||||
which is not covered here. |
||||
|
||||
## Backup a bootable disk |
||||
|
||||
Let's say your disk is called sdX. |
||||
|
||||
### Backup Master Boot Record |
||||
The MBR is a portion of bootable binary code. Do not try to read it with any |
||||
text editor ! They may slightly modify it, rendering it unbootable. |
||||
|
||||
$ dd if=/dev/sdX of=path/to/disk_backup.mbr bs=446 count=1 |
||||
|
||||
### Backup partition table |
||||
|
||||
$ sfdisk -d /dev/sdX > path/to/disk_backup.partition_table.txt |
||||
|
||||
### Backup useful partitions |
||||
|
||||
For each partition (numbered Y) : |
||||
|
||||
$ dd if=/dev/sdXY of=path/to/partition_backup.dd status=progress |
||||
|
||||
Compressed variant : |
||||
|
||||
$ dd if=/dev/sdXY status=progress | gzip > path/to/partition_backup.dd.gz |
||||
|
||||
## Restore a backed up disk into sdX |
||||
|
||||
Edit `path/to/disk_backup.partition_table.txt` to reflect the partitions you |
||||
will actually restore. Then : |
||||
|
||||
$ dd if=path/to/disk_backup.mbr of=/dev/sdX |
||||
$ sfdisk /dev/sdX < path/to/disk_backup.partition_table.txt |
||||
|
||||
You might have to eject and re-plug the disk to see the added |
||||
partitions. Then for each of those : |
||||
|
||||
$ zcat if=arch_zero_pY.dd.gz | dd of=/dev/sdXY |
||||
|
||||
And finally restore MBR : |
||||
|
||||
$ dd if=path/to/disk_backup.mbr of=/dev/sdX bs=446 count=1 |
||||
|
@ -0,0 +1,101 @@ |
||||
+++ |
||||
title = "Access Point on a Raspberry Pi" |
||||
date = 2020-09-26 |
||||
tags = ["admin"] |
||||
+++ |
||||
The Raspberry Pi family who have wireless capabilities can all serve as wifi |
||||
Access Points - meaning some other devices connect to it, perhaps accessing |
||||
internet through it. We'll leave the "forward internet" part for another |
||||
time, and concentrate on serving wifi connections as a hotspot. |
||||
|
||||
If what you need is a "rescue" Access Point to be able to log back into the |
||||
machine, you might rather check my other article on [RPi basics][arch-on-rpi], |
||||
where you will see how to let `wpa_supplicant` fall back to being an Access |
||||
Point if it can't connect to any. |
||||
|
||||
And for now let's use `hostapd`, a package to implement and advertise a wifi |
||||
access point. It might be a better option for production use since it's a |
||||
dedicated tool, whereas `wpa_supplicant` is a bit out of its league here. |
||||
|
||||
`hostapd` needs the interface to be up and configured with an IP. So we'll |
||||
configure `systemd-networkd` to do that, and by the way propose an IP address to |
||||
clients that get connected to our AP. Change |
||||
`/etc/systemd/network/05-wlan.network` so that it reads : |
||||
``` systemd |
||||
[Network] |
||||
Address=192.168.1.1 |
||||
DHCPServer=true |
||||
IPMasquerade=true |
||||
IPForward=true |
||||
|
||||
[DHCPServer] |
||||
PoolOffset=100 |
||||
PoolSize=20 |
||||
EmitDNS=true |
||||
``` |
||||
Now `hostapd` won't work (at least in this configuration) if you have |
||||
`wpa_supplicant` installed. Go on and |
||||
``` |
||||
# pacman -Rs wpa_supplicant |
||||
``` |
||||
And last, you will need to tweak `/etc/hostapd/hostapd.conf`. This one was taken |
||||
from [Archwiki's][aw-ap], and curated for RPi zero W : |
||||
``` |
||||
interface=wlan0 |
||||
#bridge=br0 |
||||
# Country code (ISO/IEC 3166-1) |
||||
country_code=FR |
||||
|
||||
# SSID to be used in IEEE 802.11 management frames |
||||
ssid=zero |
||||
wpa_passphrase=YOUR_PASSWORD_HERE |
||||
|
||||
# Driver interface type (hostap/wired/none/nl80211/bsd) - default hostapd |
||||
#driver=rtl871xdrv |
||||
#driver=nl80211 |
||||
|
||||
# Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz) |
||||
# RPi zero W only supports b |
||||
hw_mode=b |
||||
# Channel number |
||||
channel=5 |
||||
# Maximum number of stations allowed |
||||
max_num_sta=5 |
||||
|
||||
# Bit field: bit0 = WPA, bit1 = WPA2 |
||||
wpa=2 |
||||
# Bit field: 1=wpa, 2=wep, 3=both |
||||
auth_algs=1 |
||||
|
||||
# Set of accepted cipher suites; disabling insecure TKIP |
||||
wpa_pairwise=CCMP |
||||
# Set of accepted key management algorithms |
||||
wpa_key_mgmt=WPA-PSK |
||||
|
||||
# hostapd event logger configuration |
||||
logger_stdout=-1 |
||||
# 0 = verbose debugging |
||||
# 1 = debugging |
||||
# 2 = informational messages |
||||
# 3 = notification |
||||
# 4 = warning |
||||
logger_stdout_level=2 |
||||
|
||||
## QoS support |
||||
#wmm_enabled=1 |
||||
## Use "iw list" to show device capabilities and modify ht_capab accordingly |
||||
#ht_capab=[HT40+][SHORT-GI-40][TX-STBC][RX-STBC1][DSSS_CCK-40] |
||||
ht_capab=[HT20][TX-STBC1][DSSS_CCK-40] |
||||
``` |
||||
|
||||
If you use a Realtek-based wifi adapter, there is a specific hostapd package for |
||||
hosts using rtl871 hardware. You will have to install it from the AUR ; you will |
||||
have to |
||||
``` |
||||
$ yay -S hostapd-rtl871xrdv |
||||
``` |
||||
_And_ modify `/etc/hostapd/hostapd.conf` to mention `driver=rtl871xdrv`. |
||||
|
||||
[arch-on-rpi]: {% post_url computing/2020-09-26-Arch-on-RPi %} |
||||
[aw-ap]: https://wiki.archlinux.org/index.php/Software_access_point |
||||
|
@ -0,0 +1,117 @@ |
||||
+++ |
||||
title = "2020 review of Raspberry Pi alternatives" |
||||
date = 2020-10-06 |
||||
tags = ["admin"] |
||||
toc = true |
||||
+++ |
||||
The Raspberry Pi is a cardboard-computer that lets you run your own operating |
||||
system. Or put it the other way around, an open computer the size of a |
||||
cigarettes pack (depending on what kind of cigarettes you refer to). But it's |
||||
quite a barebone system, mostly lacking proper ADC and DAC, and battery |
||||
management. Extensions allow to stack those features, but they're overall quite |
||||
expensive. It might be time to consider alternatives to the rPi Zero in the same |
||||
... hem league ? |
||||
|
||||
## rPi Zero features |
||||
The smallest form factor is the Raspberry Pi Zero at 60x32mm. The RPi Zero w is |
||||
a wifi+bluetooth model for a 40% increase in price, topping at ... €11. |
||||
|
||||
The rPi Zero roughly shares its processor with the initial rPi, that is a |
||||
Broadcom BCM2835 SoC, with an ARMv6 core. Those were the first ARMs to support |
||||
floating point calculations. |
||||
|
||||
The various available models all lack an ADC and a proper DAC, although most do |
||||
have an audio output that can do CD-like audio quality using PWM modulation - |
||||
that is, pulsing alternative 0s and 1s so quickly it feels like an analog output |
||||
on an average (see [this rPi post][rpi-audio] on rPi audio quality). |
||||
|
||||
Power management is also quite barebone : you power them through a micro-USB |
||||
port and they can't control a battery's charging state. You can plug a battery |
||||
rescue pack to feed it, but then you will have to charge it separately, and if |
||||
you ever want to craft a case too put it in, this will hardly fit into the same |
||||
proportions. |
||||
|
||||
Various constructors provide extensions called pHats that stack on the rPi using |
||||
its GPIO pins. If you need a self-contained system with a battery and power |
||||
control, you will need a pHat specialized in power management, like |
||||
[pibat][pibat] that bills $26 for 1600mAh, or [pisugar2][pisugar2], very neat |
||||
but $40 worth for 900mAh. Another interesting extension is the [UPS power |
||||
expansion board][ups], hosting 1Ah for $20. The PiHut also sells a [LiPo |
||||
shim][shim] to keep the functionality packed. |
||||
|
||||
## Banana Pi M2 |
||||
Banana Pi obviously tries to challenge the features of the Raspberry Pi. The |
||||
firm developed the [bPi M2][bpi-m2-wiki] that's a revamped version of the rPi |
||||
Zero, featuring a ARMv7 quad-core processor, and a soft power-on button. It's |
||||
$18 worth at [Aliexpress][bpi-m2]. |
||||
|
||||
## Arietta |
||||
Acme Systems propose the [Arietta][acme-arietta]. This board sports the |
||||
Microchip AT91SAM9G25 processor, based on the ARM926 core, which belongs to the |
||||
ARMv5 series. Acme assures customers that it is guaranteed to run the latest |
||||
linux kernel for quite long. It is supported by the ArchARM distribution. Acme's |
||||
site offers a €25 basic model. |
||||
|
||||
## NanoPi |
||||
The [NanoPi NEO][nanopi] features an Allwinner H3 processor, with a quad-core |
||||
cortex-A7 ARM core. It's 40x40mm large, and has an ethernet and a USB-A socket. |
||||
It seems able to do basic power management, according to notes in |
||||
[nanopi.io][nanopi.io]. [friendlyARM's wiki][nanopi-wiki] states that the nanopi |
||||
has a builtin audio codec and that it should output sound on the lineout pins. |
||||
Its price is reported between $10 and $50. |
||||
|
||||
There also is a NanoPi AIR with wifi instead of ethernet and USB sockets, |
||||
available for $20 at [friendlyARM][nanopi-air]. Specs are otherwise similar, |
||||
it's just slimmer. |
||||
|
||||
## Banana Pi D1 |
||||
But then there is the [bPi D1][bpi-d1-wiki]. This challenger is 38x38mm big, and |
||||
ships with an HD camera, analog audio in and out pins, power control management |
||||
and a 3.7V li-ion battery socket, a power switch and a user-definable soft |
||||
button, RTC (basically, a clock that remembers time between boots), a |
||||
microphone, and a CMOS image sensor (I don't even know what this means). I saw |
||||
it at $20 at [Aliexpress][bpi-d1]. |
||||
|
||||
This is really a huge feature pack for such a small package. The question that |
||||
rises is : what will we be able to run on this platform ? The processor is an |
||||
Anyka AK3918, with an ARM chipset the generation just before rPi Zero, and a |
||||
bunch of peripherals as stated in the [specs][ak3918] pdf description. Looking |
||||
into it, I found out it uses the same ARM core (ARM926EJ is an ARM9, which is |
||||
part of ARMv5) as the [Olinuxino][olinuxino] board from Olimex, and the same |
||||
amount of RAM (64MB). The latter is supported in [ArchARM][olinuxino-archarm], |
||||
with a caveat due to the limited RAM : localegen would need swap, so there's no |
||||
UTF-8 support. I wonder if distcc would help here. Mainline Arch linux does |
||||
provide a cross-platform distcc installation : `distccd-alarm-armv5`. But this |
||||
doesn't prove Arch will run on Anyka's chip. ArchARM install instructions for |
||||
various ARMv5 boards imply installing uboot in the boot sector. This should |
||||
work, as it relies on pretty standard settings. |
||||
|
||||
I don't know what to expect from the audio hardware capabilities either, since |
||||
they're embedded into Anyka's chip, and probably differ from Olinuxino's i.MX233 |
||||
chip audio hardware. I'll keep this article up with any information I might get |
||||
on this subject. |
||||
|
||||
## Conclusion |
||||
The bPi D1 seems to be the most fitted product, but there is no guarantee it |
||||
will run your preferate OS. The NanoPi seems to be the next interesting choice, |
||||
but then you will have to deal with power management. In the end, the rPi Zero |
||||
is still a favorable choice, years and years after its design. |
||||
|
||||
|
||||
[rpi-audio]: https://www.raspberrypi.org/forums/viewtopic.php?t=195178 |
||||
[pibat]: https://www.ebay.fr/itm/PiBat-Raspberry-Pi-Zero-W-1600mAh-LiPo-Battery-Shield-5-6-Hours-of-autonomy/283842482176 |
||||
[pisugar2]: https://www.tindie.com/products/pisugar/pisugar2-battery-for-raspberry-pi-zero/ |
||||
[ups]: https://www.alibaba.com/product-detail/Raspberry-Pi-Zero-UPS-Power-Expansion_62093794216.html |
||||
[shim]: https://thepihut.com/products/lipo-shim?ref=isp_rel_prd&isp_ref_pos=2 |
||||
[nanopi]: https://www.alibaba.com/product-detail/Open-Source-H3-Quad-core-Cortex_62093676606.html |
||||
[nanopi.io]: http://nanopi.io/nanopi-neo-air.html |
||||
[nanopi-air]: https://www.friendlyarm.com/index.php?route=product/product&path=69&product_id=151 |
||||
[nanopi-wiki]: http://wiki.friendlyarm.com/wiki/index.php/NanoPi_NEO_Air#Custom_welcome_message |
||||
[bpi-m2-wiki]: http://wiki.banana-pi.org/Getting_Started_with_M2_Zero |
||||
[bpi-m2]: https://www.aliexpress.com/item/32839074880.html |
||||
[bpi-d1-wiki]: http://wiki.banana-pi.org/Banana_PI_D1 |
||||
[bpi-d1]: https://www.aliexpress.com/item/32240699956.html |
||||
[ak3918]: https://drive.google.com/file/d/0B4PAo2nW2KfnbGJpa19FWWpwWDQ/view |
||||
[olinuxino]: https://www.olimex.com/wiki/IMX233 |
||||
[olinuxino-archarm]: https://archlinuxarm.org/platforms/armv5/olinuxino |
||||
[acme-arietta]: https://www.acmesystems.it/arietta |
@ -0,0 +1,75 @@ |
||||
+++ |
||||
title = "SVG" |
||||
date = 2020-04-13 |
||||
tags = ["html"] |
||||
+++ |
||||
Pour intégrer un fichier SVG et pouvoir le redimensionner, on peut le mettre |
||||
dans un conteneur HTML. Il va s'arranger pour tout afficher et centrer le |
||||
résultat, comme ça : |
||||
|
||||
``` html |
||||
<span class="svgContainer" style="background-color: darkgrey;"> |
||||
<svg viewbox="0 0 100 100" width="20em" height="10em"> |
||||
<path fill="magenta" stroke="black" d="M0,0 H100 V100 H0 Z"> |
||||
</svg> |
||||
</span> |
||||
``` |
||||
Centrage vertical : |
||||
|
||||
``` html |
||||
<span class="svgContainer" style="background-color: darkgrey;"> |
||||
<svg viewbox="0 0 100 100" width="10em" height="20em"> |
||||
<path fill="magenta" stroke="black" d="M0,0 H100 V100 H0 Z"> |
||||
</svg> |
||||
</span> |
||||
``` |
||||
|
||||
Si on veut qu'il "stretche" sur son conteneur, on peut ajouter l'option |
||||
`preserveAspectRatio = none` : |
||||
|
||||
``` html |
||||
<span id="svgContainer"> |
||||
<svg viewbox="0 0 100 100" width="20em" height="10em" |
||||
preserveAspectRatio="none"> |
||||
<path fill="magenta" stroke="black" d="M0,0 H100 V100 H0 Z"> |
||||
</svg> |
||||
</span> |
||||
``` |
||||
|
||||
# Position et taille |
||||
|
||||
L'élément SVG a des attributs transitionables, comme `svg.width` et `svg.height`, |
||||
`svg.style.left` et `svg.style.top`. En utilisant le mode `position = relative`, |
||||
on peut déplacer l'élément tout en gardant sa place initiale réservée, comme si |
||||
rien n'avait changé : |
||||
|
||||
``` html |
||||
<span class="svgContainer" style="background-color: darkgrey;"> |
||||
<svg viewbox="0 0 100 100" width="10em" height="10em" |
||||
style="position: relative; left: -3em; top: 1.5em;"> |
||||
<path fill="magenta" stroke="black" d="M0,0 H100 V100 H0 Z"> |
||||
</svg> |
||||
</span> |
||||
``` |
||||
|
||||
On peut enfin utiliser le mode `position = absolute`, où le conteneur est |
||||
arraché au flot du document. Il est positionné relativement à son plus proche |
||||
parent positionné. La plupart des éléments ont par défaut `position = static`, |
||||
qui la laisse à sa place dans le flot du document. Donc le positionnement se |
||||
fait le plus souvent par rapport à l'élément `<body>`. |
||||
|
||||
Ici l'élément est `absolute` et son parent direct `relative`. On remarque que le |
||||
span svgContainer a maintenant une hauteur nulle ; en fait en position |
||||
`absolute`, le svg n'est plus contenu dans svgContainer mais dans `<body>` ; |
||||
svgContainer est donc vide. |
||||
|
||||
``` html |
||||
<span class="svgContainer" |
||||
style="background-color: darkgrey; position: relative"> |
||||
<svg viewbox="0 0 100 100" width="10em" height="10em" |
||||
style="position: absolute; left: 35vw; top: -4vw;"> |
||||
<path fill="magenta" stroke="black" d="M0,0 H100 V100 H0 Z"> |
||||
</svg> |
||||
</span> |
||||
``` |
||||
|
@ -0,0 +1,135 @@ |
||||
+++ |
||||
title = "transitions II - javascript" |
||||
date = 2020-04-13 |
||||
# layout: code |
||||
+++ |
||||
<script> |
||||
function move_into(el, destEl) { |
||||
let destStyle = window.getComputedStyle(destEl); |
||||
let destHeight = destStyle.height; |
||||
let startPos = el.getBoundingClientRect(); |
||||
console.log('start point', startPos.left, startPos.top); |
||||
|
||||
/* |
||||
destEl.style.backgroundColor = "grey"; |
||||
destEl.style.position = "relative"; |
||||
destEl.style.height = destEl.offsetHeight + "px"; |
||||
destEl.style.width = destEl.offsetWidth + "px"; |
||||
destEl.style.transition = "all 1s"; |
||||
void destEl.offsetLeft; |
||||
//destEl.focus(); |
||||
|
||||
// DEBUG Trigger transition on destination ? |
||||
destEl.appendChild(el); |
||||
*/ |
||||
|
||||
destEl.appendChild(el); |
||||
destFinalHeight = destEl.offsetHeight; |
||||
console.log(destHeight, destFinalHeight); |
||||
destEl.style.transition = "0s"; |
||||
destEl.style.height = destHeight; |
||||
void destEl.offsetHeight; |
||||
destEl.style.transition = "1s"; |
||||
destEl.style.height = destFinalHeight + "px"; |
||||
|
||||
let endPos = el.getBoundingClientRect(); |
||||
console.log("way to go", endPos.left-startPos.left, endPos.top-startPos.top); |
||||
|
||||
// We are in final position, we need to move back to inital pos. |
||||
el.style.position = "relative"; |
||||
el.style.left = startPos.left - endPos.left + "px"; |
||||
el.style.top = startPos.top - endPos.top + "px"; |
||||
|
||||
// Let javascript notice current state : |
||||
void el.offsetLeft; |
||||
|
||||
el.addEventListener('transitionend', function log() { |
||||
el.removeEventListener('transitionend', log); |
||||
console.log("après", destEl.offsetHeight); |
||||
}); |
||||
// Trigger transition : move to final position. |
||||
el.style.transition = "all 1s"; |
||||
el.style.left = "0"; |
||||
el.style.top = "0"; |
||||
} |
||||
</script> |
||||
|
||||
On veut maintenant déplacer un div dans un autre avec une transition sur le |
||||
déplacement. Ppur ça, on va dans l'ordre : |
||||
- récupérer les coordonnées de départ par rapport à la racine du document |
||||
- arracher l'élément et le mettre dans le conteneur d'arrivée |
||||
- le basculer en position relative et lui passer les coodonnées de départ |
||||
- déclencher le déplacement jusqu'à sa position dans le conteneur d'arrivée |
||||
|
||||
{% highlight js linenos %} |
||||
function move_into(el, destEl) { |
||||
let startPos = el.getBoundingClientRect(); |
||||
console.log('start point', startPos.left, startPos.top); |
||||
|
||||
destEl.appendChild(el); |
||||
let endPos = el.getBoundingClientRect(); |
||||
console.log("way to go", endPos.left-startPos.left, endPos.top-startPos.top); |
||||
|
||||
// We are in final position, we need to move back to inital pos. |
||||
el.style.position = "relative"; |
||||
el.style.left = startPos.left - endPos.left + "px"; |
||||
el.style.top = startPos.top - endPos.top + "px"; |
||||
el.style.transition = "all 1s"; |
||||
|
||||
// Let javascript notice current state : |
||||
void el.offsetLeft; |
||||
|
||||
// Trigger transition : move to final position. |
||||
el.style.left = "0"; |
||||
el.style.top = "0"; |
||||
} |
||||
{% endhighlight %} |
||||
|
||||
Si vous lisez l'anglais, ce qu'on vient de voir devrait être assez lisible. Le |
||||
plus obscur est certainement l'utilisation de `void`. Ligne 16, On utilise la |
||||
commande |
||||
|
||||
{% highlight js %} |
||||
void el.offsetLeft; |
||||
{% endhighlight %} |
||||
|
||||
pour obliger javascript à constater l'état de |
||||
départ, avant de rentrer les coordonnées d'arrivée. En effet, sinon javascript |
||||
ne voit pas le changement de valeur des coordonnées, il ne voit que la valeur |
||||
finale. Et dans ce cas, pas d'animation de la transition, on va directement aux |
||||
valeurs finales. Voilà le fragment de html et sa prévisualisation : |
||||
|
||||
``` html |
||||
<style> |
||||
.half { |
||||
display: inline-block; |
||||
width: 45%; |
||||
} |
||||
.large { |
||||
height: 8em; |
||||
} |
||||
</style> |
||||
<div class="half"> |
||||
<button onclick="move_into(fleche, cible)"> |
||||
bouger la flèche |
||||
</button> |
||||
<div id="cible"> |
||||
texte dans la cible |
||||
</div> |
||||
</div> |
||||
<div class="half"> |
||||
<div class="large"> |
||||
un div pour prendre un peu de place |
||||
</div> |
||||
<div id="fleche" class="large"> |
||||
texte dans la flèche |
||||
</div> |
||||
</div> |
||||
``` |
||||
|
||||
Je dois dire que je trouve assez frustrant de ne pas pouvoir déclencher les |
||||
transitions sur les éléments impactés. J'ai essayé, je n'ai rien trouvé de |
||||
concluant. |
||||
|
||||
[la suite]({% post_url html/2020-04-13-transition-3 %}) |
||||
|
@ -0,0 +1,93 @@ |
||||
+++ |
||||
title = "transitions III - css" |
||||
date = 2020-04-13 |
||||
# layout: code |
||||
+++ |
||||
<script src="{% link html/transition-3.js %}" type="text/javascript"></script> |
||||
|
||||
On a la possibilité de donner des réglages par défaut à nos transitions en css ; |
||||
ça permet d'alléger le javascript, et de contrôler les paramètres de la |
||||
transition depuis le css. |
||||
|
||||
Ici `move_into(el, destEl)` va utiliser une classe qui durera le temps de la |
||||
transition, qui s'appelera `transition-deplacement`. On pourra y stocker des |
||||
valeurs par défaut pour la transition. Par exemple, |
||||
|
||||
{% highlight js %} |
||||
el.style.top = ""; |
||||
{% endhighlight %} |
||||
|
||||
donnera à l'élément `el` la valeur `top` décrite dans le css. Comme `el` |
||||
appartient à la classe `transition-deplacement` qui dit que `top = 0;` eh ben |
||||
voilà. |
||||
|
||||
{% highlight js %} |
||||
function move_into(el, destEl) { |
||||
if(el.classList.contains('transition-deplacement')) { |
||||
console.log('! already movin'); |
||||
} else { |
||||
el.classList.add('transition-deplacement'); |
||||
|
||||
// Do the job |
||||
|
||||
el.addEventListener('transitionend', function declasse() { |
||||
el.removeEventListener('transitionend', declasse); |
||||
el.classList.remove('transition-deplacement'); |
||||
console.log("élément", el, "déclassé"); |
||||
}); |
||||
} |
||||
{% endhighlight %} |
||||
|
||||
Pour finir, `toggle(el, destEl)` utilisera une autre classe pour déterminer si |
||||
`el` a déjà été ouvert, la classe `ouvert`. Si oui, il le mettra dans le parent |
||||
direct de `destEl`. |
||||
|
||||
Dans ce cas, on n'utilise même pas cette classe dans le css. Elle ne sert qu'à |
||||
javascript. |
||||
|
||||
{% highlight js %} |
||||
function toggle(el, destEl) { |
||||
if(el.classList.contains('ouvert')) { |
||||
el.classList.remove('ouvert'); |
||||
move_into(el, destEl.parentElement); |
||||
} else { |
||||
el.classList.add('ouvert'); |
||||
move_into(el, destEl); |
||||
} |
||||
} |
||||
{% endhighlight %} |
||||
|
||||
Tous ces points sont dans le fichier javascript inclus dans la page à partir de |
||||
[là]({% link html/transition-3.js %}). Et maintenant le html et la |
||||
prévisualisation : |
||||
``` html |
||||
<style> |
||||
.transition-deplacement { |
||||
position: relative; |
||||
transition: 1s; |
||||
left: 0; |
||||
top: 0; |
||||
} |
||||
.min { |
||||
display: inline-block; |
||||
width: 10em; |
||||
height: 5em; |
||||
} |
||||
</style> |
||||
<div> |
||||
<button onclick="toggle(fleche, cible)"> |
||||
bouger le div 1 |
||||
</button> |
||||
<div id="cible" class="min"> |
||||
texte de la cible |
||||
</div> |
||||
<div class="min"> |
||||
un div pour prendre un peu de place |
||||
</div> |
||||
<div id="fleche" class="min"> |
||||
texte de la flèche |
||||
</div> |
||||
</div> |
||||
``` |
||||
[la suite]({% post_url html/2020-04-13-transition-4 %}) |
||||
|
@ -0,0 +1,103 @@ |
||||
+++ |
||||
title = "transition IV - SVG" |
||||
date = 2020-04-13 |
||||
# layout: code |
||||
+++ |
||||
<script src="{% link html/transition-4.js %}" type="text/javascript"></script> |
||||
|
||||
Et si c'est un objet SVG qu'on veut déplacer ? Ça fonctionne encore, en |
||||
utilisant `svg.getBoundingClientRect()` pour récupérer la position, et la même |
||||
fonction pour initialiser la position. Pour clarifier, on crée des fonctions qui |
||||
font le tri en fonction du type de l'élément : `initialize_state(el)` et |
||||
`tell_coords(el)`. |
||||
|
||||
{% highlight js %} |
||||
function tell_coords(el) { |
||||
var coords = new Object(); |
||||
if(el.viewBox === undefined) { |
||||
coords["x"] = el.offsetLeft; |
||||
coords["y"] = el.offsetTop; |
||||
} else { |
||||
let geom = el.getBoundingClientRect(); |
||||
coords["x"] = geom.left; |
||||
coords["y"] = geom.top; |
||||
} |
||||
return coords; |
||||
} |
||||
function initialize_state(el) { |
||||
if(el.viewBox === undefined) { |
||||
void el.offsetWidth; |
||||
} else { |
||||
el.getBoundingClientRect(); |
||||
} |
||||
} |
||||
{% endhighlight %} |
||||
|
||||
Ensuite `move_into(el, destEl)` fait le même travail qu'avant, mais |
||||
en appelant les fonctions qu'on a construites. |
||||
|
||||
{% highlight js %} |
||||
function move_into(el, destEl) { |
||||
if(el.classList.contains('transition-deplacement')) { |
||||
console.log('! already movin'); |
||||
} else { |
||||
el.classList.add('transition-deplacement'); |
||||
let coords_depart = tell_coords(el); |
||||
console.log('déplacement depuis', el.parentElement); |
||||
//console.log('déplacement depuis ', coords_depart); |
||||
|
||||
destEl.appendChild(el); |
||||
el.style.position = "relative"; |
||||
let coords_arrivee = tell_coords(el); |
||||
console.log("destination", destEl); |
||||
//console.log("destination ", coords_arrivee); |
||||
|
||||
el.style.transition = "all 0s"; |
||||
el.style.left = coords_depart["x"] - coords_arrivee["x"] + "px"; |
||||
el.style.top = coords_depart["y"] - coords_arrivee["y"] + "px"; |
||||
el.addEventListener('transitionend', function declasse() { |
||||
el.removeEventListener('transitionend', declasse); |
||||
el.classList.remove('transition-deplacement'); |
||||
console.log("élément", el, "déclassé"); |
||||
}); |
||||
initialize_state(el); |
||||
el.style.transition = ""; // Reset to css value. |
||||
el.style.left = ""; |
||||
el.style.top = ""; |
||||
} |
||||
} |
||||
{% endhighlight %} |
||||
|
||||
Le javascript inclus complet est toujours [là][js]. |
||||
Enfin le html : |
||||
|
||||
```html |
||||
<style> |
||||
span { |
||||
display: inline-block; |
||||
} |
||||
.minimum { |
||||
width: 10em; |
||||
height: 3em; |
||||
} |
||||
.transition-deplacement { |
||||
position: relative; |
||||
top: 0; |
||||
left: 0; |
||||
transition: 1s; |
||||
} |
||||
</style> |
||||
<div> |
||||
<button onclick="toggle(fleche, cible)">bouger</button> |
||||
<span class="minimum"> |
||||
un span pour prendre un peu de place |
||||
</span> |
||||
<span id="cible" class="minimum"> |
||||
texte du span d'arrivée |
||||
</span> |
||||
<svg id="fleche" viewBox="0 0 100 100"> |
||||
<path d="M0,0 H100 V100 H0 Z" fill="transparent" stroke="black" /> |
||||
</svg> |
||||
</div> |
||||
``` |
||||
[js]: {% link html/transition-4.js %} |
@ -0,0 +1,60 @@ |
||||
+++ |
||||
tite = "transition" |
||||
date = 2020-04-13 |
||||
# layout: code |
||||
+++ |
||||
<script> |
||||
function move_span() { |
||||
let span = document.querySelector("#movableSpan"); |
||||
if(span.classList.contains('slided')) { |
||||
span.classList.remove('slided'); |
||||
} else { |
||||
span.classList.add('slided'); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
Voilà un sujet délicat. Les transitions permettent de changer un paramètre de |
||||
manière progressive sur un temps donné. |
||||
|
||||
Pour créer ces effets avec accélération matérielle, il faut les écrire comme |
||||
effets css. Il y a pour ça les paramètres `transitionProperty` et |
||||
`transitionDuration`, avec le paramètre abrégé `transition`. La transition est |
||||
déclenchée la prochaine fois qu'un élément prend la classe impliquée. Par |
||||
exemple : |
||||
|
||||
``` html |
||||
<script> |
||||
function move_span(span) { |
||||
let span = document.querySelector("#movableSpan"); |
||||
if(span.classList.contains('slided')) { |
||||
span.classList.remove('slided'); |
||||
} else { |
||||
span.classList.add('slided'); |
||||
} |
||||
} |
||||
</script> |
||||
<style> |
||||
#movableSpan { |
||||
position: relative; |
||||
top: 0; |
||||
left: 0; |
||||
transition: 1s; |
||||
} |
||||
.slided { |
||||
transition: 1s; |
||||
top: 5em !important; |
||||
} |
||||
</style> |
||||
<div> |
||||
<button onclick="move_span()">bouger le span 1</button> |
||||
<span id="movableSpan" > |
||||
texte du span 1 |
||||
</span> |
||||
<span> |
||||
texte du span 2 |
||||
</span> |
||||
</div> |
||||
``` |
||||
[la suite]({% post_url html/2020-04-13-transition-2 %}) |
||||
|
@ -0,0 +1,152 @@ |
||||
+++ |
||||
title = "Language Server Protocols in Vim" |
||||
date = 2020-09-16 |
||||
tags = ["admin", "vim"] |
||||
+++ |
||||
Language Server Protocol aka LSP is an open source initiative of ... Microsoft. |
||||
Each "language server" defines every detail of a programming language's syntax |
||||
and grammar, like its keywords, and how you put them together. This knowledge |
||||
base is in turn used by editor helpers who can for instance lint your code or |
||||
suggest completion, a field of features labeled "Intellisense". |
||||
|
||||
Since Vim 8, programmers may run asynchronous code in vimscript. This feature is |
||||
leveraged in some plugins that use LSP to lint or suggest while you type. Let's |
||||
name [vim-lsp][vim-lsp], [coc.vim][coc.vim] and [vim-ale][vim-ale]. |
||||
|
||||
## Vim plugins on your own |
||||
|
||||
It is worth noting that coc.vim provides a language server installer (vim-lsp |
||||
also has `vim-lsp-settings` that _should_ do the job, although I was not able to |
||||
run it on my system). It is very likely that your operating system chips some, |
||||
at least if it's a linux. _But_ if you already have Vim running on your OS, it |
||||
should be quite simple to use one of the formers with a plugin manager. If you |
||||
don't have any plugin manager, be advised that Vim 8 ships its own plugin system |
||||
; you just need to unpack the plugin's main directory in |
||||
`~/.vim/pack/PACK_NAME/start/`. `PACK_NAME` can be anything you want, and its |
||||
subdirectory `start` can be filled with as many plugins as you please. In this |
||||
case, you could do for instance : |
||||
``` |
||||
$ mkdir -p ~/.vim/pack/LSP/start |
||||
$ cd ~/.vim/pack/LSP/start |
||||
$ git clone https://github.com/neoclide/coc.nvim |
||||
``` |
||||
And that's it ! (If you don't have git neither a terminal but still have Vim, |
||||
you can still go at [coc.vim][coc.vim] and manually unpack the latest version's |
||||
tarball you Vim's config dir - `pack` should lie next to `ftplugin` et al). |
||||
|
||||
## Vim plugins in your package manager |
||||
|
||||
You might prefer to use your system's package manager to install plugins, so |
||||
that they get upgraded with the rest of the system. In this case you could do |
||||
for instance in [Arch linux](http://archlinux.org) : |
||||
``` |
||||
# pacman -S vim-ale |
||||
``` |
||||
Once properly set up, vim-ale will lint the file you are editing in the |
||||
background (using Vim 8 async processes). It will send all errors and warnings |
||||
found while you type to the location window, a temporary "subwindow" that you |
||||
can check back with `:lw`. See `:help location-list-window` to get more info on |
||||
this feature. You can jump to the last diagnosed error with `:ll`, and navigate |
||||
with `:lla` and `:lne`. |
||||
|
||||
The nice bonus is the completion option. It lists all matching expressions with |
||||
what you began typing, and as you scroll the list, Vim's `preview-window` pops |
||||
up and gives a description of whatever you are coding - objects in python, |
||||
functions in bash, etc. |
||||
|
||||
I'm afraid vim-ale on its own doesn't know much about any language at all. You |
||||
will have to feed it with a knowledge provider for each of the languages you |
||||
care for. See the next chapter about this. |
||||
|
||||
Meanwhile, I tweaked vim-ale to populate Vim's default completion sequence, |
||||
remap completion shortcuts, and let the preview window be opened at the bottom |
||||
with the following content into `~/.vim/plugin/ale.vim` : |
||||
``` vimscript |
||||
" vim-ale settings |
||||
|
||||
" Enable ale completion where available. |
||||
" This setting must be set before ALE is loaded. |
||||
let g:ale_completion_enabled = 1 |
||||
let g:ale_completion_autoimport = 1 |
||||
|
||||
" Lets ctrl-x ctrl-o open ale completion |
||||
set omnifunc=ale#completion#OmniFunc |
||||
|
||||
" Remap tab, shift-tab and enter to useful completion shortcuts when |
||||
" completion already began : |
||||
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>" |
||||
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>" |
||||
inoremap <expr> <cr> pumvisible() ? "\<C-y>" : "\<cr>" |
||||
|
||||
" Let preview window open on the bottom (J) |
||||
augroup previewWindowPosition |
||||
au! |
||||
autocmd BufWinEnter * call PreviewWindowPosition() |
||||
augroup END |
||||
function! PreviewWindowPosition() |
||||
if &previewwindow |
||||
wincmd J |
||||
endif |
||||
endfunction |
||||
``` |
||||
|
||||
## Installing language servers |
||||
|
||||
You could install your favourite language's language server. For example under |
||||
Arch : |
||||
|
||||
# pacman -S bash-language-server |
||||
|
||||
And then let Vim know about it. Create `~/.vim/plugin/language-servers.vim` and |
||||
paste : |
||||
|
||||
``` vimscript |
||||
" Support for bash |
||||
if executable('bash-language-server') |
||||
augroup LspBash |
||||
autocmd! |
||||
autocmd User lsp_setup call lsp#register_server({ |
||||
\ 'name': 'bash-language-server', |
||||
\ 'cmd': {server_info->[&shell, &shellcmdflag, 'bash-language-server start']}, |
||||
\ 'allowlist': ['sh'], |
||||
\ }) |
||||
augroup END |
||||
endif |
||||
``` |
||||
Arch users also get python, C/C++ (in the `ccls` package), and LaTeX language |
||||
servers in community. I also found `rls-git` for Rust language server, HTML from |
||||
Microsoft's VSCode, and javascript with `typescript-language-server-bin` in the |
||||
AUR. |
||||
|
||||
vim-lsp maintains a list of usual Vim LSP support scripts [on its |
||||
wiki][vim-lsp-support]. Once you've picked up your choice and installed it it's |
||||
mostly a matter of copy and paste. |
||||
|
||||
Well, all this sounded quite systematic ! But ... vim-ale also maintains a list |
||||
of working linters [here][vim-ale-linters]. And it turns out that before |
||||
language servers, there was quite a bunch of linters doing the job all right. |
||||
And vim-ale is able to run those "traditional" linters while you type. And they |
||||
allow completion while you type. They also bear a smaller footprint. This |
||||
discussion could go more into the details of each language support, but finally |
||||
why go for the big overhead of this open source infrastructure ? |
||||
|
||||
I ended up replacing language servers as follows : |
||||
|
||||
| language | LSP implementation | linter | |
||||
|---|---|---| |
||||
| bash | `bash-language-server` | `shellcheck` | |
||||
| C/C++ | ? | `ccls` | |
||||
| css | `vscode-css-languageserver-bin` | `prettier` | |
||||
| javascript | `typescript-language-server-bin` | `typescript`'s tsserver | |
||||
| json | `vscode-json-language-server` | `prettier` | |
||||
| html | `vscode-html-languageserver-bin` | `htmlhint` | |
||||
| markdown | ? | `mdl` | |
||||
| python | `python-language-server` | `autopep8` | |
||||
| vala | `vala-language-server` | `uncrustify` | |
||||
| vimscript | `vim-language-server` | `vint` | |
||||
|
||||
[vim-ale]: https://github.com/dense-analysis/ale |
||||
[vim-ale-linters]: https://github.com/dense-analysis/ale/blob/master/supported-tools.md |
||||
[vim-lsp]: https://github.com/prabirshrestha/vim-lsp |
||||
[vim-lsp-support]: https://github.com/prabirshrestha/vim-lsp/wiki/Servers |
||||
[coc.vim]: https://github.com/neoclide/coc.nvim |
@ -0,0 +1,30 @@ |
||||
+++ |
||||
title = "Vim resources" |
||||
date = 2020-09-17 |
||||
tags = ["admin", "vim"] |
||||
+++ |
||||
I'll be dropping links to pages proposing interesting insights of Vim in here. |
||||
|
||||
Let's start with a very classical one : [Learn Vimscript the Hard Way][hardway] |
||||
by Steve Losh, that I find very useful to give hints when you know precisely |
||||
what you want to do. I also dig the spirit. |
||||
|
||||
Then there is the cheatsheet approach. I bookmarked Rico's |
||||
[devhints.io][devhints] page on Vimscript, that I come back to when there's a |
||||
need. There's also this [regex tutorial][regex] that is nice to bookmark, as |
||||
regex are really something you're not in a hurry to master. |
||||
|
||||
Oh and then there's vimways.org's [quickfix shortcuts][quickfix] that introduces |
||||
the thing with its sarcastic tone, quite a journey. |
||||
|
||||
Lastly I also found [this useful post][jakobgm] on integrating git into vim. |
||||
Then I remembered I had found this wizard's way of teaching |
||||
[operators][operators] in Vim, coming out with comments on orthogonality between |
||||
operators and motions. |
||||
|
||||
[hardway]: https://learnvimscriptthehardway.stevelosh.com/ |
||||
[devhints]: https://devhints.io/vimscript-functions |
||||
[regex]: https://medium.com/factory-mind/regex-tutorial-a-simple-cheatsheet-by-examples-649dc1c3f285 |
||||
[quickfix]: https://vimways.org/2018/colder-quickfix-lists/ |
||||
[jakobgm]: https://jakobgm.com/posts/vim/git-integration/ |
||||
[operators]: https://whileimautomaton.net/2008/11/vimm3/operator |
@ -0,0 +1,5 @@ |
||||
--- |
||||
title: "Search" |
||||
--- |
||||
|
||||
{{<search>}} |
@ -0,0 +1,48 @@ |
||||
function move_into(el, destEl) { |
||||
if(el.classList.contains('transition-deplacement')) { |
||||
console.log('! already movin'); |
||||
} else { |
||||
|
||||
el.classList.add('transition-deplacement'); |
||||
|
||||
let startPos = el.getBoundingClientRect(); |
||||
console.log('déplacement depuis', startPos.left, startPos.top); |
||||
|
||||
destEl.appendChild(el); |
||||
let endPos = el.getBoundingClientRect(); |
||||
console.log("destination", endPos.left, endPos.top); |
||||
|
||||
// Remove transitional class on completion.
|
||||
el.addEventListener('transitionend', function declasse() { |
||||
console.log("élément", el, "déclassé"); |
||||
el.removeEventListener('transitionend', declasse); |
||||
el.classList.remove('transition-deplacement'); |
||||
}); |
||||
|
||||
// Move to initial position, coords from final position :
|
||||
console.log( |
||||
startPos.left - endPos.left + "px", |
||||
startPos.top - endPos.top + "px"); |
||||
el.style.transition = "0s"; |
||||
el.style.left = startPos.left - endPos.left + "px"; |
||||
el.style.top = startPos.top - endPos.top + "px"; |
||||
|
||||
// Let javascript notice current state.
|
||||
void el.offsetLeft; |
||||
|
||||
// Trigger transition : move to final position.
|
||||
el.style.transition = ""; |
||||
el.style.left = ""; |
||||
el.style.top = ""; |
||||
} |
||||
} |
||||
function toggle(el, destEl) { |
||||
if(el.classList.contains('ouvert')) { |
||||
el.classList.remove('ouvert'); |
||||
move_into(el, destEl.parentElement); |
||||
} else { |
||||
el.classList.add('ouvert'); |
||||
move_into(el, destEl); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,59 @@ |
||||
function tell_coords(el) { |
||||
var coords = new Object(); |
||||
if(el.viewBox === undefined) { |
||||
coords.left = el.offsetLeft; |
||||
coords.top = el.offsetTop; |
||||
} else { |
||||
let geom = el.getBoundingClientRect(); |
||||
coords.left = geom.left; |
||||
coords.top = geom.top; |
||||
} |
||||
return coords; |
||||
} |
||||
function initialize_state(el) { |
||||
if(el.viewBox === undefined) { |
||||
void el.offsetWidth; |
||||
} else { |
||||
el.getBoundingClientRect(); |
||||
} |
||||
} |
||||
function move_into(el, destEl) { |
||||
if(el.classList.contains('transition-deplacement')) { |
||||
console.log('! already movin'); |
||||
} else { |
||||
el.classList.add('transition-deplacement'); |
||||
let startPos = tell_coords(el); |
||||
console.log('déplacement depuis', el.parentElement); |
||||
//console.log('déplacement depuis ', startPos);
|
||||
|
||||
destEl.appendChild(el); |
||||
let endPos = tell_coords(el); |
||||
console.log("destination", destEl); |
||||
//console.log("destination ", endPos);
|
||||
|
||||
el.style.transition = "all 0s"; |
||||
el.style.left = startPos.left - endPos.left + "px"; |
||||
el.style.top = startPos.top - endPos.top + "px"; |
||||
el.addEventListener('transitionend', function declasse() { |
||||
el.removeEventListener('transitionend', declasse); |
||||
el.classList.remove('transition-deplacement'); |
||||
console.log("élément", el, "déclassé"); |
||||
}); |
||||
|
||||
initialize_state(el); |
||||
el.style.transition = ""; // Reset to css value.
|
||||
el.style.left = ""; |
||||
el.style.top = ""; |
||||
} |
||||
} |
||||
function toggle(el, destEl) { |
||||
if(el.classList.contains('ouvert')) { |
||||
//move_into(depart, arrivee.parentElement);
|
||||
el.classList.remove('ouvert'); |
||||
move_into(el, destEl.parentElement); |
||||
} else { |
||||
el.classList.add('ouvert'); |
||||
move_into(el, destEl); |
||||
} |
||||
} |
||||
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1 @@ |
||||
Subproject commit e7af87b290f47acb2a2463c3cb8bcbed4405713c |
Loading…
Reference in new issue