Instantly share code, notes, and snippets.
Last active
June 13, 2026 17:45
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save UCyborg/97da8842516a1d1760cead0354b1a6d6 to your computer and use it in GitHub Desktop.
Shutdown button - user chrome script for Mozilla Firefox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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