feat: add evil bit support

This commit is contained in:
Jet 2026-05-17 15:27:14 -07:00
parent da72bc5483
commit d6f032e52e
No known key found for this signature in database
4 changed files with 239 additions and 0 deletions

View file

@ -0,0 +1,151 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js';
const EVIL_BIT_CTL = '@evilBitCtl@';
const PKEXEC = '/run/wrappers/bin/pkexec';
const STATE_FILE = '/run/evil-bit-toggle/enabled';
const ICON_NAME = 'dialog-warning-symbolic';
const EvilBitToggle = GObject.registerClass(
class EvilBitToggle extends QuickSettings.QuickToggle {
constructor() {
super({
title: 'Evil Bit',
subtitle: 'RFC 3514: good',
iconName: ICON_NAME,
toggleMode: true,
});
this._syncing = false;
this._pending = false;
this._destroyed = false;
this._syncFromState();
this._checkedId = this.connect('notify::checked', () => {
if (this._syncing)
return;
this._setEvilBit(this.checked);
});
}
_isEnabled() {
return GLib.file_test(STATE_FILE, GLib.FileTest.EXISTS);
}
_syncFromState() {
this._syncing = true;
this.checked = this._isEnabled();
this._syncing = false;
this._syncSubtitle();
}
_setEvilBit(enabled) {
if (this._pending)
return;
this._pending = true;
this.reactive = false;
this.subtitle = 'Applying...';
const action = enabled ? 'enable' : 'disable';
let proc;
try {
proc = Gio.Subprocess.new(
[PKEXEC, EVIL_BIT_CTL, action],
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
);
} catch (error) {
this._finishSetEvilBit(error);
return;
}
proc.communicate_utf8_async(null, null, (subprocess, result) => {
try {
const [, , stderr] = subprocess.communicate_utf8_finish(result);
if (!subprocess.get_successful())
throw new Error(stderr.trim() || `evil-bitctl ${action} failed`);
this._finishSetEvilBit(null);
} catch (error) {
this._finishSetEvilBit(error);
}
});
}
_finishSetEvilBit(error) {
if (error)
console.warn(`Unable to toggle Evil Bit: ${error.message}`);
if (this._destroyed)
return;
this._pending = false;
this.reactive = true;
this._syncFromState();
}
_syncSubtitle() {
this.subtitle = this.checked ? 'RFC 3514: evil' : 'RFC 3514: good';
}
destroy() {
this._destroyed = true;
if (this._checkedId) {
this.disconnect(this._checkedId);
this._checkedId = null;
}
super.destroy();
}
});
const EvilBitIndicator = GObject.registerClass(
class EvilBitIndicator extends QuickSettings.SystemIndicator {
constructor() {
super();
this._indicator = this._addIndicator();
this._indicator.icon_name = ICON_NAME;
this._toggle = new EvilBitToggle();
this._indicator.visible = this._toggle.checked;
this._checkedId = this._toggle.connect('notify::checked', () => {
this._indicator.visible = this._toggle.checked;
});
this.quickSettingsItems.push(this._toggle);
}
destroy() {
if (this._checkedId) {
this._toggle.disconnect(this._checkedId);
this._checkedId = null;
}
this.quickSettingsItems.forEach(item => item.destroy());
this._toggle = null;
super.destroy();
}
});
export default class EvilBitToggleExtension extends Extension {
enable() {
this._indicator = new EvilBitIndicator();
Main.panel.statusArea.quickSettings.addExternalIndicator(this._indicator);
}
disable() {
this._indicator.destroy();
this._indicator = null;
}
}

View file

@ -0,0 +1,6 @@
{
"uuid": "evil-bit-toggle@jetpham.github.com",
"name": "Evil Bit Toggle",
"description": "Adds a Quick Settings toggle for the RFC 3514 evil bit.",
"shell-version": ["49"]
}

View file

@ -182,6 +182,7 @@ in
"appindicatorsupport@rgcjonas.gmail.com" "appindicatorsupport@rgcjonas.gmail.com"
"gnome-shell-extension-maximized-by-default@stiggimy.github.com" "gnome-shell-extension-maximized-by-default@stiggimy.github.com"
"no-titlebar-when-maximized@alec.ninja" "no-titlebar-when-maximized@alec.ninja"
"evil-bit-toggle@jetpham.github.com"
"reduced-motion-toggle@jetpham.github.com" "reduced-motion-toggle@jetpham.github.com"
]; ];
favorite-apps = favoriteApps; favorite-apps = favoriteApps;

View file

@ -6,6 +6,68 @@
}: }:
let let
evilBitCtl = pkgs.writeShellApplication {
name = "evil-bitctl";
runtimeInputs = [
pkgs.coreutils
pkgs.nftables
];
text = ''
state_dir=/run/evil-bit-toggle
state_file="$state_dir/enabled"
table=evil_bit
chain=output
usage() {
printf 'Usage: evil-bitctl {enable|disable|status}\n' >&2
exit 64
}
enable() {
nft add table ip "$table" 2>/dev/null || true
if nft list chain ip "$table" "$chain" >/dev/null 2>&1; then
nft flush chain ip "$table" "$chain"
else
nft add chain ip "$table" "$chain" '{ type route hook output priority mangle; policy accept; }'
fi
nft add rule ip "$table" "$chain" ip frag-off set ip frag-off '|' 0x8000
install -d -m 0755 "$state_dir"
touch "$state_file"
}
disable() {
nft delete table ip "$table" 2>/dev/null || true
rm -f "$state_file"
rmdir "$state_dir" 2>/dev/null || true
}
status() {
if [ -e "$state_file" ] && nft list table ip "$table" >/dev/null 2>&1; then
printf 'enabled\n'
else
printf 'disabled\n'
fi
}
case "''${1:-}" in
enable)
enable
;;
disable)
disable
;;
status)
status
;;
*)
usage
;;
esac
'';
};
reducedMotionToggleExtension = pkgs.stdenvNoCC.mkDerivation { reducedMotionToggleExtension = pkgs.stdenvNoCC.mkDerivation {
pname = "gnome-shell-extension-reduced-motion-toggle"; pname = "gnome-shell-extension-reduced-motion-toggle";
version = "1"; version = "1";
@ -20,6 +82,24 @@ let
runHook postInstall runHook postInstall
''; '';
}; };
evilBitToggleExtension = pkgs.stdenvNoCC.mkDerivation {
pname = "gnome-shell-extension-evil-bit-toggle";
version = "1";
src = ../gnome-extensions/evil-bit-toggle;
installPhase = ''
runHook preInstall
substituteInPlace extension.js \
--replace-fail @evilBitCtl@ ${evilBitCtl}/bin/evil-bitctl
mkdir -p "$out/share/gnome-shell/extensions/evil-bit-toggle@jetpham.github.com"
cp -r . "$out/share/gnome-shell/extensions/evil-bit-toggle@jetpham.github.com"
runHook postInstall
'';
};
in in
{ {
@ -105,6 +185,7 @@ in
gnomeExtensions.system-monitor-next gnomeExtensions.system-monitor-next
gnomeExtensions.tailscale-qs gnomeExtensions.tailscale-qs
gnomeExtensions.wifi-qrcode gnomeExtensions.wifi-qrcode
evilBitToggleExtension
reducedMotionToggleExtension reducedMotionToggleExtension
nerd-fonts.commit-mono nerd-fonts.commit-mono