Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions battery-actions/Settings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ ColumnLayout {
NTextInput {
Layout.fillWidth: true
fontFamily: Settings.data.ui.fontFixed
label: tr("settings.plugged_in_label", "When Plugged In")
description: tr("settings.plugged_in_desc", "The script that will be executed when the system is plugged in.")
label: tr("settings.plugged_in_label")
description: tr("settings.plugged_in_desc")
placeholderText: "command1; command2; /path/to/script; ..."
text: root.editPluggedInScript
onTextChanged: root.editPluggedInScript = text
}
NTextInput {
Layout.fillWidth: true
fontFamily: Settings.data.ui.fontFixed
label: tr("settings.on_battery_label", "When Oe Battery")
description: tr("settings.on_battery_desc", "The script that will be executed when the system is on battery power.")
label: tr("settings.on_battery_label")
description: tr("settings.on_battery_desc")
placeholderText: "command1; command2; /path/to/script; ..."
text: root.editOnBatteryScript
onTextChanged: root.editOnBatteryScript = text
Expand All @@ -37,25 +37,25 @@ ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
NLabel {
label: tr("settings.env_vars_title", "Additional Environment Variables Provided")
label: tr("settings.env_vars_title")
}

NLabel {
description: `$BAT_PERCENTAGE: ${tr("settings.env_vars_percentage", "Battery Percentage")}`
description: `$BAT_PERCENTAGE: ${tr("settings.env_vars_percentage")}`
}
NLabel {
description: `$BAT_STATE: ${tr("settings.env_vars_state", "Battery State (Charging, Discharging, Fully Charged, etc.)")}`
description: `$BAT_STATE: ${tr("settings.env_vars_state")}`
}
NLabel {
description: `$BAT_RATE: ${tr("settings.env_vars_rate", "Battery Charge rate (in Watts)")}`
description: `$BAT_RATE: ${tr("settings.env_vars_rate")}`
}
NLabel {
description: `$BAT_PATH: ${tr("settings.env_vars_path", "OS Battery path (/sys/class/power_supply/...)")}`
description: `$BAT_PATH: ${tr("settings.env_vars_path")}`
}
}

function tr(key, fallback) {
return pluginApi?.tr(key) || fallback;
function tr(key) {
return pluginApi?.tr(key);
}

function saveSettings() {
Expand Down
36 changes: 36 additions & 0 deletions mirror-mirror/BarWidget.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Quickshell
import qs.Commons
import qs.Services.UI
import qs.Widgets

NIconButton {
id: root

property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""

readonly property bool mirroring: pluginApi?.mainInstance?.mirroringActive ?? false
readonly property string screenName: screen?.name ?? ""

baseSize: Style.getCapsuleHeightForScreen(screenName)
applyUiScale: false
customRadius: Style.radiusL
icon: "screen-share"
tooltipText: mirroring
? pluginApi?.tr("bar.tooltip.active")
: pluginApi?.tr("bar.tooltip.inactive")
tooltipDirection: BarService.getTooltipDirection(screenName)

colorBg: mirroring ? Color.mError : Style.capsuleColor
colorFg: mirroring ? Color.mOnError : Color.mOnSurface
colorBgHover: mirroring ? Color.mError : Color.mHover
colorFgHover: mirroring ? Color.mOnError : Color.mOnHover
colorBorder: mirroring ? Color.mError : Style.capsuleBorderColor
colorBorderHover: mirroring ? Color.mError : Style.capsuleBorderColor

onClicked: {
pluginApi?.openPanel(root.screen, root);
}
}
126 changes: 126 additions & 0 deletions mirror-mirror/Main.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import QtQuick
import Quickshell.Io

Item {
id: root

property var pluginApi: null
property bool mirroringActive: mirrorProcess.running
property string sourceOutput: ""
property string destinationOutput: ""
property string lastError: ""
property bool stopRequested: false
property bool suppressNextStderr: false

function isIgnorableWlMirrorLine(line) {
const normalized = String(line || "").trim().toLowerCase();
if (normalized.length === 0) {
return true;
}

return normalized.indexOf("mirror-extcopy::init(): missing ext_image_copy_capture protocol") !== -1
|| normalized.indexOf("mirror::auto_backend_fallback():") !== -1;
}

function isBackendAttemptFailureLine(line) {
const normalized = String(line || "").trim().toLowerCase();
if (normalized.length === 0) {
return false;
}

return normalized.indexOf("failed") !== -1 && normalized.indexOf("backend") !== -1
|| normalized.indexOf("missing") !== -1 && normalized.indexOf("protocol") !== -1
|| normalized.indexOf("no supported") !== -1 && normalized.indexOf("backend") !== -1;
}

function startMirror(source, destination) {
if (mirrorProcess.running) {
root.lastError = root.pluginApi?.tr("main.error.alreadyMirroring");
return;
}

if (!source || !destination) {
root.lastError = root.pluginApi?.tr("main.error.selectBothMonitors");
return;
}

if (source === destination) {
root.lastError = root.pluginApi?.tr("main.error.sameMonitor");
return;
}

root.lastError = "";

root.sourceOutput = source;
root.destinationOutput = destination;

mirrorProcess.command = [
"wl-mirror",
"--fullscreen-output",
destination,
source
];
root.stopRequested = false;
mirrorProcess.running = true;
}

function stopMirror() {
if (mirrorProcess.running) {
root.stopRequested = true;
root.suppressNextStderr = true;
mirrorProcess.signal(15);
}
}

Process {
id: mirrorProcess
running: false
command: []

onExited: (exitCode, exitStatus) => {
if (root.stopRequested) {
root.stopRequested = false;
return;
}

if (exitCode !== 0 && exitStatus !== Process.NormalExit) {
root.lastError = root.pluginApi?.tr("main.error.unexpectedExit");
}
}

stderr: StdioCollector {
onStreamFinished: {
if (root.suppressNextStderr) {
root.suppressNextStderr = false;
return;
}

const lines = text.split("\n");
const remaining = [];
let sawBackendAttemptFailure = false;

for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.length === 0 || root.isIgnorableWlMirrorLine(line)) {
continue;
}

if (root.isBackendAttemptFailureLine(line)) {
sawBackendAttemptFailure = true;
continue;
}

remaining.push(line);
}

const msg = remaining.join("\n").trim();
if (msg.length > 0) {
root.lastError = msg;
} else if (sawBackendAttemptFailure) {
root.lastError = root.pluginApi?.tr("main.error.noCompatibleBackend");
}
}
}
}

}
Loading