-
Notifications
You must be signed in to change notification settings - Fork 98
feat(power): configurable power button behavior + rename Power Mode → Power Options (OS-463) #2671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
elibosley
wants to merge
4
commits into
master
Choose a base branch
from
feature/os-463-power-options
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3b2d096
feat(power): add configurable power button behavior (OS-463)
elibosley 70281a7
fix(power): stop elogind from handling the power button
elibosley c4fa856
feat(power): add reboot/suspend actions and a plugin extension hook
elibosley 1e7413b
refactor(power): make sleep a plugin-provided action, not core
elibosley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| Menu="PowerMode:2" | ||
| Title="Power Button" | ||
| Tag="icon-power" | ||
| --- | ||
| <?PHP | ||
| /* Copyright 2005-2025, Lime Technology | ||
| * Copyright 2012-2025, Bergware International. | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU General Public License version 2, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| */ | ||
| ?> | ||
| <? | ||
| $cur = _var($powermode,'powerbutton','shutdown'); | ||
| ?> | ||
|
|
||
| <form markdown="1" name="PowerButton" method="POST" action="/update.php" target="progressFrame"> | ||
| <input type="hidden" name="#file" value="dynamix/dynamix.cfg"> | ||
| <input type="hidden" name="#section" value="powermode"> | ||
|
|
||
| _(Physical power button)_: | ||
| : <select name="powerbutton" id="powerbutton" onchange="powerbuttonExtra(this)"> | ||
| <?=mk_option($cur, "shutdown", _('Shut down').' ('._('default').')')?> | ||
| <?=mk_option($cur, "reboot", _('Reboot'))?> | ||
| <?=mk_option($cur, "ignore", _('Do nothing'))?> | ||
| <?php | ||
| // Extension point: plugins add <option> lines for custom actions. | ||
| // In scope: $cur (current powerbutton value). See /etc/acpi/powerbutton.d/README. | ||
| foreach (glob('/usr/local/emhttp/plugins/*/include/powerbutton-option.php') ?: [] as $frag) include $frag; | ||
| ?> | ||
| </select> | ||
|
|
||
| :powerbutton_behavior_help: | ||
|
|
||
| <?php | ||
| // Extension point: plugins add config rows shown when their action is selected. | ||
| // Each fragment outputs: <div class="powerbutton-extra" data-action="<action>" markdown="1"> ...fields... </div> | ||
| foreach (glob('/usr/local/emhttp/plugins/*/include/powerbutton-extra.php') ?: [] as $frag) include $frag; | ||
| ?> | ||
|
|
||
| | ||
| : <span class="inline-block"> | ||
| <input type="submit" name="#apply" value="_(Apply)_" disabled> | ||
| <input type="button" value="_(Done)_" onclick="done()"> | ||
| </span> | ||
| </form> | ||
|
|
||
| <script> | ||
| function powerbuttonExtra(sel) { | ||
| var v = sel.value; | ||
| $('.powerbutton-extra').each(function(){ | ||
| $(this).toggle($(this).data('action') === v); | ||
| }); | ||
| } | ||
| $(function(){ powerbuttonExtra(document.getElementById('powerbutton')); }); | ||
| </script> |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,77 +1,4 @@ | ||
| Menu="OtherSettings" | ||
| Type="xmenu" | ||
| Title="Power Mode" | ||
| Title="Power Options" | ||
| Icon="icon-energysaving" | ||
| Tag="icon-energysaving" | ||
| --- | ||
| <?PHP | ||
| /* Copyright 2005-2023, Lime Technology | ||
| * Copyright 2012-2023, Bergware International. | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU General Public License version 2, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| */ | ||
| ?> | ||
| <? | ||
| $cpufreq = '/sys/devices/system/cpu/cpu0/cpufreq'; | ||
| $current = exec("cat $cpufreq/scaling_governor 2>/dev/null"); | ||
| exec("cat $cpufreq/scaling_available_governors 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | sort -u",$governor); | ||
|
|
||
| function value(...$modes) { | ||
| global $current, $governor; | ||
| $checked = $value = ''; | ||
| $disabled = ' disabled'; | ||
| foreach ($modes as $mode) { | ||
| if ($mode==$current) $checked = ' checked'; | ||
| if (in_array($mode,$governor)) {$value = "value=\"$mode\""; $disabled = '';} | ||
| } | ||
| return $value.$checked.$disabled; | ||
| } | ||
| ?> | ||
| <script> | ||
| function preparePowermode(form) { | ||
| $(form).find('[name="#arg[1]"]').val(form.powermode.value); | ||
| } | ||
| $(function(){ | ||
| $('input[type=radio]').each(function(){ | ||
| if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>"); | ||
| }); | ||
| <?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?> | ||
| $('#vm').show(); | ||
| <?endif;?> | ||
| }); | ||
| </script> | ||
|
|
||
| <form markdown="1" name="PowerMode" method="POST" action="/update.php" target="progressFrame" onsubmit="preparePowermode(this)"> | ||
| <input type="hidden" name="#file" value="dynamix/dynamix.cfg"> | ||
| <input type="hidden" name="#section" value="powermode"> | ||
| <input type="hidden" name="#command" value="/webGui/scripts/powermode"> | ||
| <input type="hidden" name="#arg[1]" value=""> | ||
|
|
||
| _(Change power mode)_: | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('powersave')?>>_(Best power efficiency)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('ondemand','balance_power')?>>_(Balanced operation)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('performance')?>>_(Best performance)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span class="inline-block"> | ||
| <input type="submit" name="#apply" value="_(Apply)_" disabled> | ||
| <input type="button" value="_(Done)_" onclick="done()"> | ||
| </span> | ||
| </form> | ||
|
|
||
| <div id="vm" class="notice">_(When running Unraid virtualized, there are no available power modes)_</div> |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| Menu="PowerMode:1" | ||
| Title="Power Mode" | ||
| Tag="icon-energysaving" | ||
| --- | ||
| <?PHP | ||
| /* Copyright 2005-2023, Lime Technology | ||
| * Copyright 2012-2023, Bergware International. | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU General Public License version 2, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| */ | ||
| ?> | ||
| <? | ||
| $cpufreq = '/sys/devices/system/cpu/cpu0/cpufreq'; | ||
| $current = exec("cat $cpufreq/scaling_governor 2>/dev/null"); | ||
| exec("cat $cpufreq/scaling_available_governors 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | sort -u",$governor); | ||
|
|
||
| function value(...$modes) { | ||
| global $current, $governor; | ||
| $checked = $value = ''; | ||
| $disabled = ' disabled'; | ||
| foreach ($modes as $mode) { | ||
| if ($mode==$current) $checked = ' checked'; | ||
| if (in_array($mode,$governor)) {$value = "value=\"$mode\""; $disabled = '';} | ||
| } | ||
| return $value.$checked.$disabled; | ||
| } | ||
| ?> | ||
| <script> | ||
| function preparePowermode(form) { | ||
| $(form).find('[name="#arg[1]"]').val(form.powermode.value); | ||
| } | ||
| $(function(){ | ||
| $('input[type=radio]').each(function(){ | ||
| if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>"); | ||
| }); | ||
| <?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?> | ||
| $('#vm').show(); | ||
| <?endif;?> | ||
| }); | ||
| </script> | ||
|
|
||
| <form markdown="1" name="PowerMode" method="POST" action="/update.php" target="progressFrame" onsubmit="preparePowermode(this)"> | ||
| <input type="hidden" name="#file" value="dynamix/dynamix.cfg"> | ||
| <input type="hidden" name="#section" value="powermode"> | ||
| <input type="hidden" name="#command" value="/webGui/scripts/powermode"> | ||
| <input type="hidden" name="#arg[1]" value=""> | ||
|
|
||
| _(Change power mode)_: | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('powersave')?>>_(Best power efficiency)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('ondemand','balance_power')?>>_(Balanced operation)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span> | ||
| <input name="powermode" type="radio"<?=value('performance')?>>_(Best performance)_ | ||
| </span> | ||
|
|
||
| | ||
| : <span class="inline-block"> | ||
| <input type="submit" name="#apply" value="_(Apply)_" disabled> | ||
| <input type="button" value="_(Done)_" onclick="done()"> | ||
| </span> | ||
| </form> | ||
|
|
||
| <div id="vm" class="notice">_(When running Unraid virtualized, there are no available power modes)_</div> | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /etc/acpi/powerbutton.d/ - Unraid power button action handlers (extension point) | ||
| ================================================================================ | ||
|
|
||
| The physical power button behavior is configured in the WebUI at | ||
| Settings > Power Options > Power Button. The selected action is stored as | ||
| [powermode] | ||
| powerbutton="<action>" | ||
| in /boot/config/plugins/dynamix/dynamix.cfg and dispatched at press time by | ||
| /etc/acpi/unraid_power_handler.sh. | ||
|
|
||
| Built-in actions: shutdown (default), reboot, ignore. | ||
|
|
||
| Actions that need their own setup or prep (suspend/sleep, running a User | ||
| Script, etc.) are NOT built in - they are provided by plugins through the | ||
| two-part hook below, so core stays free of plugin-specific or hardware-risky | ||
| behavior. | ||
|
|
||
| Plugins can ADD or OVERRIDE actions in two parts: | ||
|
|
||
| 1) Action handler (required) | ||
| Ship an executable here, named after the action value: | ||
| /etc/acpi/powerbutton.d/<action> | ||
| It runs when the power button is pressed and that action is selected. | ||
| (e.g. the S3 Sleep plugin ships powerbutton.d/sleep to suspend with its | ||
| own array-aware prep.) | ||
|
|
||
| 2) WebUI option (so users can pick it) | ||
| Add the action to the selector by shipping: | ||
| /usr/local/emhttp/plugins/<plugin>/include/powerbutton-option.php | ||
| which echoes one or more options, e.g.: | ||
| <?=mk_option($cur, "script", _('Run script'))?> | ||
| ($cur is the currently saved powerbutton value.) | ||
|
|
||
| For extra fields shown only when your action is selected, also ship: | ||
| /usr/local/emhttp/plugins/<plugin>/include/powerbutton-extra.php | ||
| which outputs a block tagged with your action value: | ||
| <div class="powerbutton-extra" data-action="script" markdown="1"> | ||
| _(Script)_: | ||
| : <select name="powerbuttonscript"> ... </select> | ||
| </div> | ||
| Fields are POSTed into dynamix.cfg [powermode] alongside powerbutton, so | ||
| your handler can read them with the same grep pattern used above. | ||
|
|
||
| Example (User Scripts plugin, "Run script" action): | ||
| - include/powerbutton-option.php : adds the "script" option + script picker | ||
| - include/powerbutton-extra.php : script selection dropdown | ||
| - /etc/acpi/powerbutton.d/script : reads powerbuttonscript= from dynamix.cfg | ||
| and executes the chosen user script |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| #!/bin/bash | ||
| # | ||
| # script: unraid_power_handler.sh | ||
| # | ||
| # Unraid ACPI event handler. Installed over /etc/acpi/acpi_handler.sh at boot | ||
| # by /etc/rc.d/rc.acpid, so it reliably overrides the stock Slackware acpid | ||
| # handler regardless of package install order. | ||
| # | ||
| # The physical power button behavior is configurable from the WebUI | ||
| # (Settings > Power Options > Power Button). The choice is stored in | ||
| # dynamix.cfg and read live on each event, so no acpid reload is needed | ||
| # when the setting changes. | ||
| # | ||
| # Plugins can add or override actions by dropping an executable handler in | ||
| # /etc/acpi/powerbutton.d/<action> - see /etc/acpi/powerbutton.d/README. | ||
| # | ||
| # LimeTech - modified for Unraid OS | ||
|
|
||
| CFG=/boot/config/plugins/dynamix/dynamix.cfg | ||
| PLUGIN_DIR=/etc/acpi/powerbutton.d | ||
|
|
||
| # acpid passes the raw event line, e.g.: "button/power PBTN 00000080 00000000" | ||
| set -- $@ | ||
| group=${1%%/*} | ||
| action=${1#*/} | ||
|
|
||
| run_powerbutton(){ | ||
| local behavior=$1 | ||
| case "$behavior" in | ||
| ignore) | ||
| logger -t acpid "Power button: ignored (Power Options setting)" | ||
| ;; | ||
| reboot) | ||
| logger -t acpid "Power button: rebooting" | ||
| /sbin/init 6 | ||
| ;; | ||
| shutdown) | ||
| logger -t acpid "Power button: initiating clean shutdown" | ||
| /sbin/init 0 | ||
| ;; | ||
| *) | ||
| # Extension point: plugin-provided action handler. | ||
| if [[ -n $behavior && -x $PLUGIN_DIR/$behavior ]]; then | ||
| logger -t acpid "Power button: running plugin action '$behavior'" | ||
| "$PLUGIN_DIR/$behavior" | ||
| else | ||
| logger -t acpid "Power button: unknown action '$behavior', ignoring" | ||
| fi | ||
| ;; | ||
| esac | ||
| } | ||
|
|
||
| case "$group" in | ||
| button) | ||
| case "$action" in | ||
| power) | ||
| behavior=$(grep -Pom1 '^powerbutton="\K[^"]+' "$CFG" 2>/dev/null) | ||
| run_powerbutton "${behavior:-shutdown}" | ||
| ;; | ||
| *) | ||
| logger -t acpid "ACPI action button/$action is not handled" | ||
| ;; | ||
| esac | ||
| ;; | ||
| *) | ||
| logger -t acpid "ACPI event $1 is not handled" | ||
| ;; | ||
| esac |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Unraid: let acpid be the sole handler of the physical power button. | ||
| # | ||
| # A power press generates both an ACPI event (handled by acpid via | ||
| # /etc/acpi/acpi_handler.sh) and an input key event (handled by elogind). | ||
| # To make the configurable power button behavior in Settings > Power Options | ||
| # work, elogind must not act on the key, leaving acpid in full control. | ||
| [Login] | ||
| HandlePowerKey=ignore | ||
| HandlePowerKeyLongPress=ignore |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unavailable-label injection currently never matches the DOM.
At Line 39,
$(this).next('span')does not find anything because the radio inputs are followed by text nodes, not<span>siblings. As written, disabled options won’t get the “unavailable” note.Suggested fix
$(function(){ $('input[type=radio]').each(function(){ - if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>"); + if ($(this).prop('disabled')) { + $(this).after(" <i>(_(unavailable)_)</i>"); + } }); <?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?>📝 Committable suggestion
🤖 Prompt for AI Agents