Skip to content

Instantly share code, notes, and snippets.

@UCyborg
Last active June 13, 2026 17:45
Show Gist options
  • Select an option

  • Save UCyborg/97da8842516a1d1760cead0354b1a6d6 to your computer and use it in GitHub Desktop.

Select an option

Save UCyborg/97da8842516a1d1760cead0354b1a6d6 to your computer and use it in GitHub Desktop.
Shutdown button - user chrome script for Mozilla Firefox
// ==UserScript==
// @name Shutdown button
// @description System shutdown button; left-click: (30 min, abort), right-click: sleep (now)
// @author UCyborg
// @startup UC.shutdownButton.init();
// @shutdown UC.shutdownButton.cleanup();
// ==/UserScript==
(function () {
"use strict";
var ctypes;
if ("importESModule" in ChromeUtils) {
try {
({ ctypes } = ChromeUtils.importESModule("resource://gre/modules/ctypes.sys.mjs"));
} catch (e) { }
}
if (!ctypes) {
ctypes = Components.utils.import("resource://gre/modules/ctypes.jsm", {}).ctypes;
}
const is64Bit = ctypes.voidptr_t.size == 8 ? true : false;
const WINABI = is64Bit ? ctypes.default_abi : ctypes.winapi_abi;
// Simple types
const BYTE = ctypes.unsigned_char;
const BOOL = ctypes.int;
const DWORD = ctypes.uint32_t;
const LONG = ctypes.int32_t;
const LPVOID = ctypes.voidptr_t;
const PVOID = ctypes.voidptr_t;
const VOID = ctypes.void_t;
const ULONG = ctypes.uint32_t;
const USHORT = ctypes.uint16_t;
const WCHAR = ctypes.jschar;
// Advanced types - based on simple types
const BOOLEAN = BYTE;
const PBYTE = BYTE.ptr;
const PBOOL = BOOL.ptr;
const PDWORD = DWORD.ptr;
const PLONG = LONG.ptr;
const PULONG = ULONG.ptr;
const PUSHORT = USHORT.ptr;
const HANDLE = LPVOID;
const PHANDLE = LPVOID.ptr;
const HRESULT = LONG;
const LPCWSTR = WCHAR.ptr;
const LPWSTR = WCHAR.ptr;
// Structs
const LUID = ctypes.StructType("LUID", [
{ "LowPart": LONG },
{ "HighPart": DWORD }
]);
const PLUID = LUID.ptr;
const LUID_AND_ATTRIBUTES = ctypes.StructType("LUID_AND_ATTRIBUTES", [
{ "Luid": LUID },
{ "Attributes": DWORD }
]);
const PLUID_AND_ATTRIBUTES = LUID_AND_ATTRIBUTES.ptr;
const TOKEN_PRIVILEGES = ctypes.StructType("TOKEN_PRIVILEGES", [
{ "PrivilegeCount": DWORD },
{ "Privileges": ctypes.ArrayType(LUID_AND_ATTRIBUTES, 1) }
]);
const PTOKEN_PRIVILEGES = TOKEN_PRIVILEGES.ptr;
// WIN32 bool values
const TRUE = 1;
const FALSE = 0;
// Token access rights
const TOKEN_QUERY = 0x0008;
const TOKEN_ADJUST_PRIVILEGES = 0x0020;
// Special privilege names
const SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
// AdjustTokenPrivileges flags
const NONE = 0x00000000;
const SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
const SE_PRIVILEGE_ENABLED = 0x00000002;
const SE_PRIVILEGE_REMOVED = 0x00000004;
const SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
// Shutdown flags
const SHUTDOWN_POWEROFF = 0x00000008;
const SHUTDOWN_HYBRID = 0x00000200;
// Shutdown error codes
const ERROR_SHUTDOWN_IS_SCHEDULED = 0x000004A6;
UC.shutdownButton = {
get shutdownButton() {
return CustomizableUI.getWidget("shutdownButton");
},
get statusinfo() {
if ("StatusPanel" in window) {
// fx61+
return StatusPanel._labelElement.value;
} else {
return XULBrowserWindow.statusTextField.label;
}
},
set statusinfo(val) {
if ("StatusPanel" in window) {
// fx61+
StatusPanel._label = val;
} else {
XULBrowserWindow.statusTextField.label = val;
}
if (this._statusinfotimer)
clearTimeout(this._statusinfotimer);
this._statusinfotimer = setTimeout(() => this.hideStatusInfo(), 2000);
this._laststatusinfo = val;
return val;
},
setShutdownPrivilege: function (enable) {
// DLL references
var kernel32 = ctypes.open("kernel32.dll");
var advapi32 = ctypes.open("advapi32.dll");
// DLL function references
var GetCurrentProcess = kernel32.declare("GetCurrentProcess", WINABI, HANDLE);
var CloseHandle = kernel32.declare("CloseHandle", WINABI, BOOL, HANDLE);
var OpenProcessToken = advapi32.declare("OpenProcessToken", WINABI, BOOL, HANDLE, DWORD, PHANDLE);
var LookupPrivilegeValueW = advapi32.declare("LookupPrivilegeValueW", WINABI, BOOL, LPCWSTR, LPCWSTR, PLUID);
var AdjustTokenPrivileges = advapi32.declare("AdjustTokenPrivileges", WINABI, BOOL, HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
var token = new HANDLE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, token.address())) {
var tkp = new TOKEN_PRIVILEGES;
if (LookupPrivilegeValueW(null, SE_SHUTDOWN_NAME, tkp.Privileges.addressOfElement(0).contents.Luid.address())) {
tkp.PrivilegeCount = 1;
tkp.Privileges.addressOfElement(0).contents.Attributes = enable ? SE_PRIVILEGE_ENABLED : NONE;
AdjustTokenPrivileges(token, FALSE, tkp.address(), 0, null, null);
}
CloseHandle(token);
}
advapi32.close();
kernel32.close();
},
init: function () {
var style = "#shutdownButton { list-style-image: url('data:image/x-icon;base64,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'); }";
this.sss = Components.classes['@mozilla.org/content/style-sheet-service;1']
.getService(Components.interfaces.nsIStyleSheetService);
var newURIParam = {
aURL: 'data:text/css,' + encodeURIComponent(style),
aOriginCharset: null,
aBaseURI: null
}
this.cssUri = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI);
this.sss.loadAndRegisterSheet(this.cssUri, this.sss.AUTHOR_SHEET);
if (this.shutdownButton)
return;
try {
CustomizableUI.createWidget({ //must run createWidget before windowListener.register because the register function needs the button added first
id: "shutdownButton",
type: "custom",
defaultArea: CustomizableUI.AREA_NAVBAR,
onBuild: function (aDocument) {
var toolbaritem;
if ("createXULElement" in aDocument) {
toolbaritem = aDocument.createXULElement("toolbarbutton");
} else {
toolbaritem = aDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "toolbarbutton");
}
const tooltiptext = "Shutdown system";
var props = {
id: "shutdownButton",
class: "toolbarbutton-1 chromeclass-toolbar-additional",
type: CustomizableUI.TYPE_TOOLBAR,
label: tooltiptext,
tooltiptext: tooltiptext,
removable: "true"
};
for (var p in props) {
toolbaritem.setAttribute(p, props[p]);
}
toolbaritem.addEventListener("click", (event) => UC.shutdownButton.shutdownSystem(event));
return toolbaritem;
},
});
} catch (e) { }
// Our process doesn't have shutdown privilege by default, so it needs to be enabled
this.setShutdownPrivilege(true);
},
cleanup: function () {
// Remove shutdown privilege
this.setShutdownPrivilege(false);
CustomizableUI.destroyWidget("shutdownButton");
this.sss.unregisterSheet(this.cssUri, this.sss.AUTHOR_SHEET);
},
shutdownSystem: function (event) {
if (!event.shiftKey) {
event.preventDefault();
} else {
return;
}
// DLL references
var advapi32 = ctypes.open("advapi32.dll");
var powrprof = ctypes.open("powrprof.dll");
// DLL function references
var InitiateShutdownW = advapi32.declare("InitiateShutdownW", WINABI, DWORD, LPWSTR, LPWSTR, DWORD, DWORD, DWORD);
var AbortSystemShutdownW = advapi32.declare("AbortSystemShutdownW", WINABI, BOOL, LPWSTR);
var SetSuspendState = powrprof.declare("SetSuspendState", WINABI, BOOLEAN, BOOLEAN, BOOLEAN, BOOLEAN);
if (event.button == 0) {
if (this.isShuttingDown) {
if (AbortSystemShutdownW(null)) {
this.isShuttingDown = false;
}
} else {
var result = InitiateShutdownW(null, null, 1800, SHUTDOWN_POWEROFF | SHUTDOWN_HYBRID, 0);
if (!result) {
this.isShuttingDown = true;
} else if (result == ERROR_SHUTDOWN_IS_SCHEDULED) {
if (!AbortSystemShutdownW(null)) {
this.isShuttingDown = true;
}
}
}
} else if (event.button == 2 && !this.isShuttingDown) {
SetSuspendState(FALSE, FALSE, FALSE);
}
powrprof.close();
advapi32.close();
},
isShuttingDown: false,
_statusinfotimer: null,
_laststatusinfo: null,
displayStatus: function (val) {
this.statusinfo = val;
},
hideStatusInfo: function () {
if (this._statusinfotimer)
clearTimeout(this._statusinfotimer);
this._statusinfotimer = null;
if (this._laststatusinfo == this.statusinfo)
this.statusinfo = "";
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment