The One with the Thoughts of Frans

Archive for Linux

Web Apps Opera Bork Edition

It’s been 18 years since Opera published their classic bork edition, to protest that MSN would have loaded properly in the browser if only it were served the same code as Internet Explorer.

“Hergee berger snooger bork,” says Mary Lambert, product line manager desktop, Opera Software. “This is a joke. However, we are trying to make an important point. The MSN site is sending Opera users what appear to be intentionally distorted pages. The Bork edition illustrates how browsers could also distort content, as the Bork edition does. The real point here is that the success of the Web depends on software and Web site developers behaving well and rising above corporate rivalry.”

Since I’m a Vivaldi user, today’s the first time I noticed that Chrome on Android artificially restricts installing webpages as apps on the homescreen. Only webpages that specify a manifest.json can receive such a hallowed treatment, instead of every single webpage ever made. For the rest of the internet, there’s only a shortcut. While the situation is not quite comparable, I found the design principle sufficiently distasteful to revive Opera’s classic bork script, in this case specifically targeting the Chrome browser.

You can put it on your website or in your TamperMonkey to remind you when you accidentally open Chrome. The classic result looks like this:

A borked Vivaldi announcement as seen in Chrome.
// http://web.archive.org/web/20050301075735/http://www.opera.com/js/bork/enchefizer.js

/* -*- mode: C++; mode: font-lock; tab-width: 4 -*-
 * 2003-02-10
 *
 * The Enchefizer code is based on a script fetched from 
 *   http://tbrowne.best.vwh.net/chef/
 * written by Andriy Rozeluk , which is
 * based on a Java version written by Josh Vura-Weis
 * , which is based on a UNIX version
 * from 1993 written by John Hagerman  and
 * Jeff Allen 
 *
 * Subsequently hacked by Opera Software to work inside a page by 
 * traversing the DOM tree, and to improve performance. 
 *
 * Typical usage is to add the following text to the bottom of a page:
 *       
 */

const classicOperaBork = () => {
    /* USER CONFIGURATION BEGINS */
    var victim=false;   // false (apply to any page) or regex to match page URL
    //var victim=/^http:\/\/(?:(?:www|msdn).microsoft.com|www.msn.com)/;
    var delay=50;       // ms between replacements, set to 0 to disable waiting
    var units=30;       // number of text nodes to translate each time
    var highlight=true; // highlight the text we're working on
    /* USER CONFIGURATION ENDS */

    var textnodes=[];   // text nodes in the doc
    var nextnode=0;     // next node to process

    function nextWordPos(line)
    {
		var p = line.search(/[ \n\t\\,<.>/?;:\'\"\[{\]}|=+\-_!@#$%^&*()~`]/);
        return p == -1 ? line.length+1 : p;
    }

    function encheferizeLine(line)
    {
		var buff="", word="", t="", out="", wp;

		while(line.length > 0)
		{
			wp = nextWordPos(line);
			word = line.substring(0,wp);
			t = line.charAt(wp);
			line = line.substring(wp+1,line.length);
			out = out + encheferizeWord(word) + t;
		}
		if(t == ".")
		{
	        out = out + "\nBork Bork Bork!";
		}

		return out;
    }

    function encheferizeWord(word)
    {
		if(word.toLowerCase() == "bork") return word;
      
		var letter, count, len, buff, i_seen, isLast;
      
		count=0;
		len=word.length;
		buff=""
		i_seen=false;
      
		while(count0){
               
				} 
			} else if(letter=='t'){
				if(count==len-2 && word.charAt(count+1)=='h'){
					buff = buff + "t";
					count+=2;
					continue;
				} else if(count<=len-3 && word.charAt(count+1)=='h'
						  && word.charAt(count+2)=='e'){
					buff = buff + "zee";
					count+=3;
					continue;
				} 
			} else if(letter=='T' && count<=len-3 && word.charAt(count+1)=='h'
					  && word.charAt(count+2)=='e'){                                                
				buff = buff + "Zee";
				count+=3;
				continue;
			} else if(letter=='v'){
				buff = buff + "f";
				count++;
				continue;
			} else if(letter=='V'){
				buff = buff + "F";
				count++;
				continue;
			} else if(letter=='w'){
				buff = buff + "v";
				count++;
				continue;
			} else if(letter=='W'){
				buff = buff + "V";
				count++;
				continue;
			}
			//End of rules.  Whatever is left stays itself
			buff = buff + letter;
			count++;
		}
		
		return(buff);
    }

    function bork()
    {
		var limit = delay == 0 ? Number.MAX_VALUE : units;
		var start=nextnode;
		var oldc = new Array();
		var n, i, candidate;

		if (highlight)
		{
			for ( n=start, i=0 ; i < limit && n < textnodes.length ; n++, i++ )
			{
				candidate = textnodes[n];
				oldc[i] = candidate.parentNode.style.backgroundColor;
			}

			for ( n=start, i=0 ; i < limit && n < textnodes.length ; n++, i++ )
			{
				candidate = textnodes[n];
				candidate.parentNode.style.backgroundColor = 'red';
			}
		}

		for ( i=0 ; i < limit && nextnode < textnodes.length ; nextnode++, i++ )
		{
			candidate = textnodes[nextnode];
            candidate.replaceData(0,candidate.length,encheferizeLine(candidate.data));
		}

		if (highlight)
		{
			for ( n=start, i=0 ; i < limit && n < textnodes.length ; n++, i++ )
			{
				candidate = textnodes[n];
				candidate.parentNode.style.backgroundColor = oldc[i];
			}
		}

		bork_more();
    }

    function bork_more()
    {
		if (nextnode < textnodes.length)
		{
			setTimeout( bork, delay );
		}
    }

    /* In large docs traversal is a bottleneck at startup; we could
       CPS it or otherwise reify the traversal state to interleave
       traversal with the translation.
	*/
    function find_textnodes(elm, acc)
    {
		if (elm.nodeType == 3)
		{
			if (!elm.data.match(/^[\s\n\r]*$/))
			{
				acc.push(elm);
			}
		}
		else
		{
			var c = elm.childNodes;
			for ( var i=0 ; i < c.length ; i++ )
			{
				find_textnodes(c.item(i),acc);
			}
		}
		return acc;
    }

    /* run page's onload handler, then do our thing */
    var res = false;

    if (typeof old_onload == "function")
	{
		res = old_onload();
	}

    if (/*window == top &&*/ (!victim || window.location.href.match(victim)) )
    {
		textnodes = find_textnodes(document.body, new Array());
		nextnode=0;
		bork_more();
    }
    return res;
}
if (window.navigator.userAgentData.brands.filter(e => e.brand === 'Google Chrome').length > 0) {
    document.addEventListener('DOMContentLoaded', classicOperaBork);
}

CommentsTags:

PipeWire on Ubuntu 21.10

On Debian 11 and Ubuntu 21.04, you can use the Debian wiki to get a basic setup working, but adapting that to newer version is a bit laborious. Instead a kind soul has already taken care of everything over at pipewire-debian. It’s also a more recent version.

So why use it? In my case, I’ve had PulseAudio crap out when having to deal with more over 20 or so things at once. PipeWire deals with load rather significantly better. I understand latency’s much better too, but that’s never bothered me too much for my fairly regular uses. What’s nice though, is that you can use JACK tools like catia to map stuff around. I don’t think PA had any graphical tools like that, and cryptic command-line commands are too much of a bother for quick one-offs. For the moment I mildly miss PulseAudio’s networking ability.

In short, I’ve switched over my laptops. But I might give it a try on my desktop too.

CommentsTags: , ,

Put Save Dialog Buttons Back at the Bottom on Debian 11 Xfce 4.16

Apparently action buttons have moved to the top now, even though people scan from the top left to the bottom right?
xfconf-query -c xsettings -p /Gtk/DialogsUseHeader -s false

CommentsTags:

The Mono repo broke my apt (and how I fixed it)

Apparently this is not completely unusual, see here. The only solution I found was to manually remove a whole bunch of oddly codependent Mono packages manually with sudo dpkg --remove --force-remove-reinstreq mono-this mono-that mono-whatever-else-shows-up. apt and apt-get were pretty stumped.

Hopefully this helps someone else.

CommentsTags: ,

Setting up Syncthing on a Debubuntu VPS

It couldn’t be much easier. First of all, you install the application using the official Apt repository. You want the repo so security updates and the like are automatically applied alongside your regular unattended upgrades. You are automatically applying security updates, aren’t you?

# Add the release PGP keys:
curl -s https://syncthing.net/release-key.txt | sudo apt-key add -

# Add the "release" channel to your APT sources:
echo "deb http://apt.syncthing.net/ syncthing release" | sudo tee /etc/apt/sources.list.d/syncthing.list

# Update and install syncthing:
sudo apt update
sudo apt install syncthing

Following installation, I’ve seen it suggested to temporarily change the configuration to allow remote web access, but it seems to me that SSH tunneling is much easier to use than changing the configuration every time you might want to enter the settings. Something like this should do. (Update: the manual already mentions it.)

ssh user@hostname -L 8500:localhost:8384

Subsequently, use localhost:8500 to access the configuration. You can of course use the same trick to make phpMyAdmin more secure. With that out of the way you can follow the manual for setting up Syncthing as a systemd service.

CommentsTags: , , ,

Bash autocomplete broken?

There’s the obvious check if bash-completion is installed, but what I didn’t realize is that .bashrc needs to contain a few things as well. If you lost it, the simple solution, at least in Debuntu, is as follows:

cp /etc/skel/.bashrc ~/

CommentsTags: ,

GUN Tar

A while back a typo evaded me. GNU Tar is a utility originally conceived for writing (t)ape (ar)chives, a type of backup, whereas GUN Tar presumably doesn’t exist. Even if it did, it wouldn’t have anything to do with the GNU part of the equation.

Curious about the prevalence of this typo, I quickly found some unrelated but very interesting pictures. A guntar is a a combination of a gun and a guitar — obviously. 😉

Btw, there’s a picture of a guntar here (via)

a guntar, a guitar with a hunting rifle on it

a guntar, a guitar with a hunting rifle on it

Here’s a random video of Frank Klepacki playing some songs from Red Alert on his “Vengeance” guitar. I’m not really into guitars, but they can be quite interesting visually.

The typo itself is actually surprisingly rare. I searched for "gun tar" -guns -military -shot -shoot -shooting -foam but it’s still showing a lot of noise. Only one result was about GNU Tar, namely and embarrassingly, the typo made it into a published book: Utilizing Open Source Tools for Online Teaching and Learning: Applying Linux. Adding an extract at the end, i.e., "gun tar" -guns -military -shot -shoot -shooting -foam extract greatly improves on the ratio of typo results.

CommentsTags: , , ,

Geany Regex Replace

Geany has a nice story about named matching groups in the manual, but those don’t seem to work in the actual find & replace dialog. Instead, you can refer to them by number. Less convenient, but generally workable.

I’ll give you a concrete example. I copied my list of games from Humble Bundle to be able to examine them better locally. I didn’t see an obvious way, so I just copied them from the website (by deactivating the user-select: none CSS property). Long story short, I ended up with a list like this:

Anomaly Korea
11 bit studios

Anomaly Warzone Earth
11 bit studios

Another World
DotEmu

Aquaria
Bit Blot

Avadon: The Black Fortress
Spiderweb Software

BIT.TRIP BEAT
Gaijin Games

Blackwell 1: Legacy
Wadjet Eye Games

Blackwell 2: Unbound
Wadjet Eye Games

Blackwell 3: Convergence
Wadjet Eye Games

Bladeslinger
Kerosene Games

Bridge Constructor
Merge Games

Using the regex pictured above, that turns into this:

Anomaly Korea;11 bit studios

Anomaly Warzone Earth;11 bit studios

Another World;DotEmu

Aquaria;Bit Blot

Avadon: The Black Fortress;Spiderweb Software

BIT.TRIP BEAT;Gaijin Games

Blackwell 1: Legacy;Wadjet Eye Games

Blackwell 2: Unbound;Wadjet Eye Games

Blackwell 3: Convergence;Wadjet Eye Games

Bladeslinger;Kerosene Games

Bridge Constructor;Merge Games

After optionally stripping out the double newlines (replace \n\n with \n), you can save the file as a .csv and open it in Calc. And there you have it. My complete list of Android games:

CommentsTags: , , , ,

Run Enpass on Debian Buster (testing)

Steam is working problem-free these days, unlike last year, but a recent update broke Enpass. Hopefully it’ll be fixed again soon, but in the meantime:

OPENSSL_CONF=/etc/ssl/ /opt/Enpass/bin/runenpass.sh

Otherwise you’ll have to contend with this fun error:

$ /opt/Enpass/bin/runenpass.sh
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
QLayout: Attempting to add QLayout "" to QPasswordEdit "", which already has a layout
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
Auto configuration failed
140475586791488:error:25066067:DSO support routines:DLFCN_LOAD:could not load the shared library:dso_dlfcn.c:185:filename(libssl_conf.so): libssl_conf.so: cannot open shared object file: No such file or directory
140475586791488:error:25070067:DSO support routines:DSO_load:could not load the shared library:dso_lib.c:244:
140475586791488:error:0E07506E:configuration file routines:MODULE_LOAD_DSO:error loading dso:conf_mod.c:285:module=ssl_conf, path=ssl_conf
140475586791488:error:0E076071:configuration file routines:MODULE_RUN:unknown module name:conf_mod.c:222:module=ssl_conf
QMutex: destroying locked mutex
Segmentation fault!

Comments (1)Tags: ,

Steam Scaling on Linux

Since I don’t use Steam that much I don’t know when it was added, but it’s nice that Steam is no longer tiny on my UHD monitor.

CommentsTags: ,

Older Entries »