From 55fd59840559477841d6fcbcbc7b0c9aea3d34a9 Mon Sep 17 00:00:00 2001 From: lafleur Date: Thu, 8 Oct 2020 23:07:35 +0200 Subject: [PATCH] imported posts from jekyll --- .gitmodules | 3 + archetypes/default.md | 6 + config.toml | 45 ++++++ content/about.md | 6 + content/posts/arch-on-rpi.md | 181 +++++++++++++++++++++++++ content/posts/local-name-resolution.md | 62 +++++++++ content/posts/midi-on-rpi.md | 137 +++++++++++++++++++ content/posts/partitions-backup.md | 55 ++++++++ content/posts/rpi-access-point.md | 101 ++++++++++++++ content/posts/rpi-alt-review.md | 117 ++++++++++++++++ content/posts/svg.md | 75 ++++++++++ content/posts/transition-2.md | 135 ++++++++++++++++++ content/posts/transition-3.md | 93 +++++++++++++ content/posts/transition-4.md | 103 ++++++++++++++ content/posts/transition.md | 60 ++++++++ content/posts/vim-language-servers.md | 152 +++++++++++++++++++++ content/posts/vim-links.md | 30 ++++ content/search.md | 5 + data/transition-3.js | 48 +++++++ data/transition-4.js | 59 ++++++++ static/images/icon.jpg | Bin 0 -> 18405 bytes themes/harbor | 1 + 22 files changed, 1474 insertions(+) create mode 100644 .gitmodules create mode 100644 archetypes/default.md create mode 100644 config.toml create mode 100644 content/about.md create mode 100644 content/posts/arch-on-rpi.md create mode 100644 content/posts/local-name-resolution.md create mode 100644 content/posts/midi-on-rpi.md create mode 100644 content/posts/partitions-backup.md create mode 100644 content/posts/rpi-access-point.md create mode 100644 content/posts/rpi-alt-review.md create mode 100644 content/posts/svg.md create mode 100644 content/posts/transition-2.md create mode 100644 content/posts/transition-3.md create mode 100644 content/posts/transition-4.md create mode 100644 content/posts/transition.md create mode 100644 content/posts/vim-language-servers.md create mode 100644 content/posts/vim-links.md create mode 100644 content/search.md create mode 100644 data/transition-3.js create mode 100644 data/transition-4.js create mode 100644 static/images/icon.jpg create mode 160000 themes/harbor diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6b38827 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "harbor"] + path = themes/harbor + url = https://github.com/matsuyoshi30/harbor diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..b8ebb0f --- /dev/null +++ b/config.toml @@ -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" + diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..7a88edf --- /dev/null +++ b/content/about.md @@ -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. diff --git a/content/posts/arch-on-rpi.md b/content/posts/arch-on-rpi.md new file mode 100644 index 0000000..20d802d --- /dev/null +++ b/content/posts/arch-on-rpi.md @@ -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 diff --git a/content/posts/local-name-resolution.md b/content/posts/local-name-resolution.md new file mode 100644 index 0000000..881971e --- /dev/null +++ b/content/posts/local-name-resolution.md @@ -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 diff --git a/content/posts/midi-on-rpi.md b/content/posts/midi-on-rpi.md new file mode 100644 index 0000000..7d1bd70 --- /dev/null +++ b/content/posts/midi-on-rpi.md @@ -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/ diff --git a/content/posts/partitions-backup.md b/content/posts/partitions-backup.md new file mode 100644 index 0000000..5b40470 --- /dev/null +++ b/content/posts/partitions-backup.md @@ -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 + diff --git a/content/posts/rpi-access-point.md b/content/posts/rpi-access-point.md new file mode 100644 index 0000000..24c8954 --- /dev/null +++ b/content/posts/rpi-access-point.md @@ -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 + diff --git a/content/posts/rpi-alt-review.md b/content/posts/rpi-alt-review.md new file mode 100644 index 0000000..0586ade --- /dev/null +++ b/content/posts/rpi-alt-review.md @@ -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 diff --git a/content/posts/svg.md b/content/posts/svg.md new file mode 100644 index 0000000..7d9560e --- /dev/null +++ b/content/posts/svg.md @@ -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 + + + + + +``` +Centrage vertical : + +``` html + + + + + +``` + +Si on veut qu'il "stretche" sur son conteneur, on peut ajouter l'option +`preserveAspectRatio = none` : + +``` html + + + + + +``` + +# 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 + + + + + +``` + +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 ``. + +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 `` ; +svgContainer est donc vide. + +``` html + + + + + +``` + diff --git a/content/posts/transition-2.md b/content/posts/transition-2.md new file mode 100644 index 0000000..215c51c --- /dev/null +++ b/content/posts/transition-2.md @@ -0,0 +1,135 @@ ++++ +title = "transitions II - javascript" +date = 2020-04-13 +# layout: code ++++ + + +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 + +
+ +
+ texte dans la cible +
+
+
+
+ un div pour prendre un peu de place +
+
+ texte dans la flèche +
+
+``` + +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 %}) + diff --git a/content/posts/transition-3.md b/content/posts/transition-3.md new file mode 100644 index 0000000..7b6f989 --- /dev/null +++ b/content/posts/transition-3.md @@ -0,0 +1,93 @@ ++++ +title = "transitions III - css" +date = 2020-04-13 +# layout: code ++++ + + +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 + +
+ +
+ texte de la cible +
+
+ un div pour prendre un peu de place +
+
+ texte de la flèche +
+
+``` +[la suite]({% post_url html/2020-04-13-transition-4 %}) + diff --git a/content/posts/transition-4.md b/content/posts/transition-4.md new file mode 100644 index 0000000..6265b18 --- /dev/null +++ b/content/posts/transition-4.md @@ -0,0 +1,103 @@ ++++ +title = "transition IV - SVG" +date = 2020-04-13 +# layout: code ++++ + + +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 + +
+ + + un span pour prendre un peu de place + + + texte du span d'arrivée + + + + +
+``` +[js]: {% link html/transition-4.js %} diff --git a/content/posts/transition.md b/content/posts/transition.md new file mode 100644 index 0000000..6352d11 --- /dev/null +++ b/content/posts/transition.md @@ -0,0 +1,60 @@ ++++ +tite = "transition" +date = 2020-04-13 +# layout: code ++++ + + +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 + + +
+ + + texte du span 1 + + + texte du span 2 + +
+``` +[la suite]({% post_url html/2020-04-13-transition-2 %}) + diff --git a/content/posts/vim-language-servers.md b/content/posts/vim-language-servers.md new file mode 100644 index 0000000..99bed9a --- /dev/null +++ b/content/posts/vim-language-servers.md @@ -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 pumvisible() ? "\" : "\" +inoremap pumvisible() ? "\" : "\" +inoremap pumvisible() ? "\" : "\" + +" 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 diff --git a/content/posts/vim-links.md b/content/posts/vim-links.md new file mode 100644 index 0000000..20d3477 --- /dev/null +++ b/content/posts/vim-links.md @@ -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 diff --git a/content/search.md b/content/search.md new file mode 100644 index 0000000..12a2d21 --- /dev/null +++ b/content/search.md @@ -0,0 +1,5 @@ +--- +title: "Search" +--- + +{{}} diff --git a/data/transition-3.js b/data/transition-3.js new file mode 100644 index 0000000..08f74ff --- /dev/null +++ b/data/transition-3.js @@ -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); + } +} + diff --git a/data/transition-4.js b/data/transition-4.js new file mode 100644 index 0000000..dd2ae90 --- /dev/null +++ b/data/transition-4.js @@ -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); + } +} + diff --git a/static/images/icon.jpg b/static/images/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ced848391aa27816f64fbc4af54b716282c78fa0 GIT binary patch literal 18405 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 literal 0 HcmV?d00001 diff --git a/themes/harbor b/themes/harbor new file mode 160000 index 0000000..e7af87b --- /dev/null +++ b/themes/harbor @@ -0,0 +1 @@ +Subproject commit e7af87b290f47acb2a2463c3cb8bcbed4405713c