imported posts from jekyll
This commit is contained in:
commit
55fd598405
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e7af87b290f47acb2a2463c3cb8bcbed4405713c
|
Loading…
Reference in New Issue