forked from Shinonome/dots-hyprland
waffles: screen snip
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
pragma Singleton
|
||||
import qs.modules.common
|
||||
import qs.modules.common.utils
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt.labs.synchronizer
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
enum Action {
|
||||
Copy,
|
||||
Edit,
|
||||
Search,
|
||||
CharRecognition,
|
||||
Record,
|
||||
RecordWithSound
|
||||
}
|
||||
|
||||
property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl
|
||||
property string fileUploadApiEndpoint: "https://uguu.se/upload"
|
||||
|
||||
function getCommand(x, y, width, height, screenshotPath, action, saveDir = "") {
|
||||
// Set command for action
|
||||
const rx = Math.round(x);
|
||||
const ry = Math.round(y);
|
||||
const rw = Math.round(width);
|
||||
const rh = Math.round(height);
|
||||
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(screenshotPath)} `
|
||||
+ `-crop ${rw}x${rh}+${rx}+${ry}`
|
||||
const cropToStdout = `${cropBase} -`
|
||||
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`
|
||||
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`
|
||||
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
|
||||
const uploadAndGetUrl = (filePath) => {
|
||||
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
|
||||
}
|
||||
const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`;
|
||||
switch (action) {
|
||||
case ScreenshotAction.Action.Copy:
|
||||
if (saveDir === "") {
|
||||
// not saving the screenshot, just copy to clipboard
|
||||
return ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`]
|
||||
break;
|
||||
}
|
||||
return [
|
||||
"bash", "-c",
|
||||
`mkdir -p '${StringUtils.shellSingleQuoteEscape(saveDir)}' && \
|
||||
saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \
|
||||
savePath="${saveDir}/$saveFileName" && \
|
||||
${cropToStdout} | tee >(wl-copy) > "$savePath" && \
|
||||
${cleanup}`
|
||||
]
|
||||
|
||||
break;
|
||||
case ScreenshotAction.Action.Edit:
|
||||
return ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]
|
||||
break;
|
||||
case ScreenshotAction.Action.Search:
|
||||
return ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(screenshotPath)})" && ${cleanup}`]
|
||||
break;
|
||||
case ScreenshotAction.Action.CharRecognition:
|
||||
return ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
|
||||
break;
|
||||
case ScreenshotAction.Action.Record:
|
||||
return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
|
||||
break;
|
||||
case ScreenshotAction.Action.RecordWithSound:
|
||||
return ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
|
||||
break;
|
||||
default:
|
||||
console.warn("[Region Selector] Unknown snip action, skipping snip.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
|
||||
Process {
|
||||
id: screenshotProc
|
||||
running: true
|
||||
property string screenshotDir: Directories.screenshotTemp
|
||||
required property ShellScreen screen
|
||||
property string screenshotPath: `${screenshotDir}/image-${screen.name}`
|
||||
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(screen.name)}' '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import QtQuick
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
|
||||
Canvas {
|
||||
id: root
|
||||
property color color: "#ffffff"
|
||||
property int dashLength: 6
|
||||
property int gapLength: 4
|
||||
property int borderWidth: 1
|
||||
|
||||
onDashLengthChanged: requestPaint()
|
||||
onGapLengthChanged: requestPaint()
|
||||
onWidthChanged: requestPaint()
|
||||
onHeightChanged: requestPaint()
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.save();
|
||||
ctx.strokeStyle = root.color;
|
||||
ctx.lineWidth = root.borderWidth;
|
||||
if (root.gapLength > 0) {
|
||||
ctx.setLineDash([root.dashLength, root.gapLength]); // Set dash pattern
|
||||
}
|
||||
ctx.strokeRect(root.borderWidth / 2, root.borderWidth / 2, width - root.borderWidth, height - root.borderWidth); // Draw it
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,16 @@ MouseArea {
|
||||
property bool automaticallyReset: true
|
||||
readonly property real dragDiffX: _dragDiffX
|
||||
readonly property real dragDiffY: _dragDiffY
|
||||
property real startX: 0
|
||||
property real startY: 0
|
||||
property real regionTopLeftX: Math.min(startX, startX + _dragDiffX)
|
||||
property real regionTopLeftY: Math.min(startY, startY + _dragDiffY)
|
||||
property real regionWidth: Math.abs(_dragDiffX)
|
||||
property real regionHeight: Math.abs(_dragDiffY)
|
||||
|
||||
signal dragPressed(diffX: real, diffY: real)
|
||||
signal dragReleased(diffX: real, diffY: real)
|
||||
|
||||
property real startX: 0
|
||||
property real startY: 0
|
||||
property bool dragging: false
|
||||
property real _dragDiffX: 0
|
||||
property real _dragDiffY: 0
|
||||
|
||||
@@ -12,19 +12,30 @@ Item {
|
||||
required property var tabButtonList
|
||||
|
||||
function incrementCurrentIndex() {
|
||||
tabBar.incrementCurrentIndex()
|
||||
tabBar.incrementCurrentIndex();
|
||||
}
|
||||
function decrementCurrentIndex() {
|
||||
tabBar.decrementCurrentIndex()
|
||||
tabBar.decrementCurrentIndex();
|
||||
}
|
||||
function setCurrentIndex(index) {
|
||||
tabBar.setCurrentIndex(index)
|
||||
tabBar.setCurrentIndex(index);
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
implicitWidth: contentItem.implicitWidth
|
||||
implicitHeight: 40
|
||||
|
||||
property Component delegate: ToolbarTabButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
current: index == root.currentIndex
|
||||
text: modelData.name
|
||||
materialSymbol: modelData.icon
|
||||
onClicked: {
|
||||
root.setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentItem
|
||||
z: 1
|
||||
@@ -33,16 +44,7 @@ Item {
|
||||
|
||||
Repeater {
|
||||
model: root.tabButtonList
|
||||
delegate: ToolbarTabButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
current: index == root.currentIndex
|
||||
text: modelData.name
|
||||
materialSymbol: modelData.icon
|
||||
onClicked: {
|
||||
root.setCurrentIndex(index)
|
||||
}
|
||||
}
|
||||
delegate: root.delegate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,23 +78,23 @@ Item {
|
||||
z: 2
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onWheel: (event) => {
|
||||
onWheel: event => {
|
||||
if (event.angleDelta.y < 0) {
|
||||
root.incrementCurrentIndex();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
root.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TabBar doesn't allow tabs to be of different sizes. Literally unusable.
|
||||
// TabBar doesn't allow tabs to be of different sizes. That's what I thought...
|
||||
// We use it only for the logic and draw stuff manually
|
||||
TabBar {
|
||||
id: tabBar
|
||||
z: -1
|
||||
background: null
|
||||
Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly
|
||||
Repeater {
|
||||
// This is to fool the TabBar that it has tabs so it does the indices properly
|
||||
model: root.tabButtonList.length
|
||||
delegate: TabButton {
|
||||
background: null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs.modules.common
|
||||
import qs.modules.common.utils
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
@@ -32,14 +33,8 @@ PanelWindow {
|
||||
property var action: RegionSelection.SnipAction.Copy
|
||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||
signal dismiss()
|
||||
|
||||
property string saveScreenshotDir: Config.options.screenSnip.savePath !== ""
|
||||
? Config.options.screenSnip.savePath
|
||||
: ""
|
||||
|
||||
property string screenshotDir: Directories.screenshotTemp
|
||||
property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl
|
||||
property string fileUploadApiEndpoint: "https://uguu.se/upload"
|
||||
property color overlayColor: "#88111111"
|
||||
property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0
|
||||
property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary
|
||||
@@ -180,10 +175,12 @@ PanelWindow {
|
||||
property real regionX: Math.min(dragStartX, draggingX)
|
||||
property real regionY: Math.min(dragStartY, draggingY)
|
||||
|
||||
Process {
|
||||
TempScreenshotProcess {
|
||||
id: screenshotProc
|
||||
running: true
|
||||
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`]
|
||||
screen: root.screen
|
||||
screenshotDir: root.screenshotDir
|
||||
screenshotPath: root.screenshotPath
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (root.enableContentRegions) imageDetectionProcess.running = true;
|
||||
root.preparationDone = !checkRecordingProc.running;
|
||||
@@ -229,6 +226,27 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function getScreenshotAction() {
|
||||
switch(root.action) {
|
||||
case RegionSelection.SnipAction.Copy:
|
||||
return ScreenshotAction.Action.Copy;
|
||||
case RegionSelection.SnipAction.Edit:
|
||||
return ScreenshotAction.Action.Edit;
|
||||
case RegionSelection.SnipAction.Search:
|
||||
return ScreenshotAction.Action.Search;
|
||||
case RegionSelection.SnipAction.CharRecognition:
|
||||
return ScreenshotAction.Action.CharRecognition;
|
||||
case RegionSelection.SnipAction.Record:
|
||||
return ScreenshotAction.Action.Record;
|
||||
case RegionSelection.SnipAction.RecordWithSound:
|
||||
return ScreenshotAction.Action.RecordWithSound;
|
||||
default:
|
||||
console.warn("[Region Selector] Unknown snip action, skipping snip.");
|
||||
root.dismiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function snip() {
|
||||
// Validity check
|
||||
if (root.regionWidth <= 0 || root.regionHeight <= 0) {
|
||||
@@ -246,62 +264,20 @@ PanelWindow {
|
||||
if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) {
|
||||
root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy;
|
||||
}
|
||||
|
||||
// Set command for action
|
||||
const rx = Math.round(root.regionX * root.monitorScale);
|
||||
const ry = Math.round(root.regionY * root.monitorScale);
|
||||
const rw = Math.round(root.regionWidth * root.monitorScale);
|
||||
const rh = Math.round(root.regionHeight * root.monitorScale);
|
||||
const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} `
|
||||
+ `-crop ${rw}x${rh}+${rx}+${ry}`
|
||||
const cropToStdout = `${cropBase} -`
|
||||
const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
||||
const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`
|
||||
const slurpRegion = `${rx},${ry} ${rw}x${rh}`
|
||||
const uploadAndGetUrl = (filePath) => {
|
||||
return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`
|
||||
}
|
||||
const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`;
|
||||
switch (root.action) {
|
||||
case RegionSelection.SnipAction.Copy:
|
||||
if (saveScreenshotDir === "") {
|
||||
// not saving the screenshot, just copy to clipboard
|
||||
snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`]
|
||||
break;
|
||||
}
|
||||
|
||||
const savePathBase = root.saveScreenshotDir
|
||||
|
||||
snipProc.command = [
|
||||
"bash", "-c",
|
||||
`mkdir -p '${StringUtils.shellSingleQuoteEscape(savePathBase)}' && \
|
||||
saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \
|
||||
savePath="${savePathBase}/$saveFileName" && \
|
||||
${cropToStdout} | tee >(wl-copy) > "$savePath" && \
|
||||
${cleanup}`
|
||||
]
|
||||
|
||||
break;
|
||||
case RegionSelection.SnipAction.Edit:
|
||||
snipProc.command = ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]
|
||||
break;
|
||||
case RegionSelection.SnipAction.Search:
|
||||
snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})" && ${cleanup}`]
|
||||
break;
|
||||
case RegionSelection.SnipAction.CharRecognition:
|
||||
snipProc.command = ["bash", "-c", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\n' '+' | sed 's/\\+$/\\n/') | wl-copy && ${cleanup}`]
|
||||
break;
|
||||
case RegionSelection.SnipAction.Record:
|
||||
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}'`]
|
||||
break;
|
||||
case RegionSelection.SnipAction.RecordWithSound:
|
||||
snipProc.command = ["bash", "-c", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]
|
||||
break;
|
||||
default:
|
||||
console.warn("[Region Selector] Unknown snip action, skipping snip.");
|
||||
root.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
const screenshotDir = Config.options.screenSnip.savePath !== "" ? //
|
||||
Config.options.screenSnip.savePath : "";
|
||||
var screenshotAction = root.getScreenshotAction();
|
||||
const command = ScreenshotAction.getCommand(
|
||||
root.regionX * root.monitorScale, //
|
||||
root.regionY * root.monitorScale, //
|
||||
root.regionWidth * root.monitorScale,//
|
||||
root.regionHeight * root.monitorScale, //
|
||||
root.screenshotPath, //
|
||||
screenshotAction, //
|
||||
screenshotDir
|
||||
)
|
||||
snipProc.command = command;
|
||||
|
||||
// Image post-processing
|
||||
snipProc.startDetached();
|
||||
|
||||
@@ -93,8 +93,8 @@ Singleton {
|
||||
property color bgPanelFooter: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelFooter : root.lightColors.bgPanelFooter, root.panelLayerTransparency)
|
||||
property color bgPanelBody: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody, root.panelLayerTransparency)
|
||||
property color bgPanelSeparator: ColorUtils.transparentize(root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, root.backgroundTransparency)
|
||||
property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
|
||||
property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency)
|
||||
property color bg0Base: root.dark ? root.darkColors.bg0 : root.lightColors.bg0
|
||||
property color bg0: ColorUtils.transparentize(bg0Base, root.backgroundTransparency)
|
||||
property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency)
|
||||
property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base
|
||||
property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency)
|
||||
|
||||
@@ -39,9 +39,10 @@ Button {
|
||||
}
|
||||
}
|
||||
property color fgColor: {
|
||||
if (!root.enabled) return root.colForegroundDisabled
|
||||
if (root.checked) return root.colForegroundToggled
|
||||
if (root.enabled) return root.colForeground
|
||||
return root.colForegroundDisabled
|
||||
return root.colForeground
|
||||
}
|
||||
property alias horizontalAlignment: buttonText.horizontalAlignment
|
||||
font {
|
||||
|
||||
@@ -76,8 +76,9 @@ Menu {
|
||||
contentItem: Item {
|
||||
implicitWidth: menuListView.implicitWidth
|
||||
implicitHeight: menuListView.implicitHeight
|
||||
ListView {
|
||||
WListView {
|
||||
id: menuListView
|
||||
interactive: contentHeight > height
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
@@ -87,6 +88,7 @@ Menu {
|
||||
topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins
|
||||
bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin
|
||||
}
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
implicitWidth: Array.from({
|
||||
length: count
|
||||
|
||||
@@ -6,6 +6,7 @@ import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
MenuItem {
|
||||
@@ -14,11 +15,11 @@ MenuItem {
|
||||
property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)
|
||||
property color colBackgroundHover: Looks.colors.bg2Hover
|
||||
property color colBackgroundActive: Looks.colors.bg2Active
|
||||
property color colBackgroundToggled: Looks.colors.accent
|
||||
property color colBackgroundToggledHover: Looks.colors.accentHover
|
||||
property color colBackgroundToggledActive: Looks.colors.accentActive
|
||||
property color colBackgroundToggled: Looks.colors.bg2Hover
|
||||
property color colBackgroundToggledHover: Looks.colors.bg2Active
|
||||
property color colBackgroundToggledActive: Looks.colors.bg2Hover
|
||||
property color colForeground: Looks.colors.fg
|
||||
property color colForegroundToggled: Looks.colors.accentFg
|
||||
property color colForegroundToggled: Looks.colors.fg
|
||||
property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4)
|
||||
property color color: {
|
||||
if (!root.enabled)
|
||||
@@ -70,27 +71,57 @@ MenuItem {
|
||||
implicitHeight: Math.max(28, contentItem.implicitHeight) + topInset + bottomInset
|
||||
implicitWidth: contentItem.implicitWidth + leftInset + rightInset + leftPadding + rightPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: contentLayout
|
||||
spacing: 12
|
||||
FluentIcon {
|
||||
id: buttonIcon
|
||||
monochrome: true
|
||||
implicitSize: 20
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.fgColor
|
||||
visible: root.icon.name !== "";
|
||||
icon: root.icon.name
|
||||
contentItem: Item {
|
||||
implicitWidth: contentLayout.implicitWidth
|
||||
implicitHeight: contentLayout.implicitHeight
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
FluentIcon {
|
||||
id: buttonIcon
|
||||
monochrome: true
|
||||
implicitSize: 20
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.fgColor
|
||||
visible: root.icon.name !== ""
|
||||
icon: root.icon.name
|
||||
}
|
||||
WText {
|
||||
id: buttonText
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
text: root.text
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
color: root.fgColor
|
||||
}
|
||||
}
|
||||
WText {
|
||||
id: buttonText
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
text: root.text
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: Looks.font.pixelSize.large
|
||||
color: root.fgColor
|
||||
|
||||
WFadeLoader {
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
leftMargin: -root.leftPadding + width
|
||||
}
|
||||
shown: root.checked
|
||||
sourceComponent: Rectangle {
|
||||
implicitWidth: 3
|
||||
implicitHeight: 3
|
||||
radius: width / 2
|
||||
color: Looks.colors.accent
|
||||
property bool forceZeroHeight: true
|
||||
height: forceZeroHeight ? 0 : Math.max(root.down ? 10 : 16, root.background.height - 18 * 2)
|
||||
Component.onCompleted: {
|
||||
forceZeroHeight = false;
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
animation: Looks.transition.resize.createObject(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
import qs.modules.common.widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real padding: 9
|
||||
property alias colBackground: background.color
|
||||
property alias spacing: toolbarLayout.spacing
|
||||
property alias radius: background.radius
|
||||
default property alias data: toolbarLayout.data
|
||||
|
||||
implicitWidth: background.implicitWidth
|
||||
implicitHeight: background.implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
implicitHeight: 50
|
||||
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
|
||||
radius: Looks.radius.large
|
||||
color: Looks.colors.bg0Base
|
||||
|
||||
border.width: 1
|
||||
border.color: Looks.colors.bg1Border
|
||||
|
||||
RowLayout {
|
||||
id: toolbarLayout
|
||||
spacing: 4
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.padding
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
|
||||
WButton {
|
||||
implicitHeight: 32
|
||||
radius: Looks.radius.medium
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
|
||||
WToolbarButton {
|
||||
id: root
|
||||
implicitWidth: height
|
||||
contentItem: Item {
|
||||
FluentIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: root.icon.name
|
||||
implicitSize: 18
|
||||
color: root.fgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.modules.common
|
||||
|
||||
TabButton {
|
||||
id: root
|
||||
|
||||
implicitWidth: 38
|
||||
implicitHeight: 32
|
||||
padding: 0
|
||||
|
||||
background: null
|
||||
contentItem: Item {
|
||||
FluentIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: root.icon.name
|
||||
color: root.icon.color
|
||||
implicitSize: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.modules.common
|
||||
|
||||
Rectangle {
|
||||
Layout.leftMargin: 4
|
||||
Layout.rightMargin: 4
|
||||
implicitHeight: 24
|
||||
implicitWidth: 1
|
||||
color: Looks.colors.bg0Border
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
|
||||
TabBar {
|
||||
id: root
|
||||
implicitHeight: 32
|
||||
|
||||
background: Rectangle {
|
||||
radius: Looks.radius.medium
|
||||
color: Looks.colors.bgPanelFooter
|
||||
border.color: ColorUtils.transparentize(Looks.colors.bg0Border, 0.7)
|
||||
border.width: 1
|
||||
|
||||
// Indicator
|
||||
Rectangle {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
leftMargin: root.currentIndex * (root.width / root.count)
|
||||
Behavior on leftMargin {
|
||||
animation: Looks.transition.resize.createObject(this)
|
||||
}
|
||||
}
|
||||
radius: Looks.radius.medium
|
||||
color: Looks.colors.bg2Base
|
||||
border.color: Looks.colors.bg0Border
|
||||
border.width: 1
|
||||
width: root.width / root.count
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 1
|
||||
}
|
||||
implicitWidth: pressDetector.containsPress ? 16 : 12
|
||||
implicitHeight: 3
|
||||
radius: height / 2
|
||||
color: Looks.colors.accent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pressDetector
|
||||
z: 9999
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import QtQuick
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int regionX
|
||||
required property int regionY
|
||||
required property int regionWidth
|
||||
required property int regionHeight
|
||||
|
||||
property bool dashed: true
|
||||
property color borderColor: "#ffffff"
|
||||
property color overlayColor: ColorUtils.transparentize("#000000", 1)
|
||||
Component.onCompleted: overlayColor = ColorUtils.transparentize("#000000", 0.4)
|
||||
Behavior on overlayColor {
|
||||
ColorAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay to darken screen
|
||||
// Base dark overlay around region
|
||||
Rectangle {
|
||||
id: darkenOverlay
|
||||
z: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: root.regionX - darkenOverlay.border.width
|
||||
topMargin: root.regionY - darkenOverlay.border.width
|
||||
}
|
||||
width: root.regionWidth + darkenOverlay.border.width * 2
|
||||
height: root.regionHeight + darkenOverlay.border.width * 2
|
||||
color: "transparent"
|
||||
border.color: root.overlayColor
|
||||
border.width: Math.max(root.width, root.height)
|
||||
}
|
||||
|
||||
// Selection border
|
||||
DashedBorder {
|
||||
id: border
|
||||
z: 2
|
||||
visible: root.regionWidth > 0 && root.regionHeight > 0
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
leftMargin: Math.round(root.regionX - borderWidth)
|
||||
topMargin: Math.round(root.regionY - borderWidth)
|
||||
}
|
||||
width: Math.round(root.regionWidth + borderWidth * 2)
|
||||
height: Math.round(root.regionHeight + borderWidth * 2)
|
||||
color: root.borderColor
|
||||
dashLength: 4
|
||||
gapLength: root.dashed ? 3 : 0
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.synchronizer
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.services
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.utils
|
||||
import qs.modules.common.widgets
|
||||
import qs.modules.waffle.looks
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
enum MediaType {
|
||||
Image,
|
||||
Video
|
||||
}
|
||||
enum ImageAction {
|
||||
Copy,
|
||||
Menu,
|
||||
CharRecognition,
|
||||
Search
|
||||
}
|
||||
enum VideoAction {
|
||||
Record,
|
||||
RecordWithSound
|
||||
}
|
||||
enum SelectionMode {
|
||||
Rect,
|
||||
Window
|
||||
}
|
||||
|
||||
signal closed
|
||||
function close() {
|
||||
root.closed();
|
||||
}
|
||||
|
||||
property var mediaType: WRegionSelectionPanel.MediaType.Image
|
||||
property var imageAction: WRegionSelectionPanel.ImageAction.Copy
|
||||
property var selectionMode: WRegionSelectionPanel.SelectionMode.Rect
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
WlrLayershell.namespace: "quickshell:regionSelector"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
top: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
// Hyprland stuff
|
||||
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)
|
||||
readonly property real monitorScale: hyprlandMonitor.scale
|
||||
readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {
|
||||
// Sort floating=true windows before others
|
||||
if (a.floating === b.floating)
|
||||
return 0;
|
||||
return a.floating ? -1 : 1;
|
||||
})
|
||||
|
||||
property string screenshotDir: Directories.screenshotTemp
|
||||
property string screenshotPath: `${root.screenshotDir}/image-${screen.name}`
|
||||
TempScreenshotProcess {
|
||||
id: screenshotProc
|
||||
running: true
|
||||
screen: root.screen
|
||||
screenshotDir: root.screenshotDir
|
||||
screenshotPath: root.screenshotPath
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
root.preparationDone = true;
|
||||
}
|
||||
}
|
||||
property bool preparationDone: false
|
||||
onPreparationDoneChanged: {
|
||||
if (!preparationDone)
|
||||
return;
|
||||
root.visible = true;
|
||||
}
|
||||
|
||||
function getScreenshotAction() {
|
||||
switch (root.mediaType) {
|
||||
case WRegionSelectionPanel.MediaType.Image:
|
||||
switch (root.imageAction) {
|
||||
case WRegionSelectionPanel.ImageAction.Copy:
|
||||
return ScreenshotAction.Action.Copy;
|
||||
case WRegionSelectionPanel.ImageAction.Menu:
|
||||
return ScreenshotAction.Action.Edit;
|
||||
case WRegionSelectionPanel.ImageAction.CharRecognition:
|
||||
return ScreenshotAction.Action.CharRecognition;
|
||||
case WRegionSelectionPanel.ImageAction.Search:
|
||||
return ScreenshotAction.Action.Search;
|
||||
default:
|
||||
return ScreenshotAction.Action.Copy;
|
||||
}
|
||||
break;
|
||||
case WRegionSelectionPanel.MediaType.Video:
|
||||
switch (root.videoAction) {
|
||||
case WRegionSelectionPanel.VideoAction.Record:
|
||||
return ScreenshotAction.Action.Record;
|
||||
case WRegionSelectionPanel.VideoAction.RecordWithSound:
|
||||
return ScreenshotAction.Action.RecordWithSound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: snipProc
|
||||
}
|
||||
|
||||
ScreencopyView {
|
||||
id: screencopyView
|
||||
anchors.fill: parent
|
||||
live: false
|
||||
captureSource: root.screen
|
||||
|
||||
focus: root.visible
|
||||
Keys.onPressed: event => { // Esc to close
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.close();
|
||||
} else if (event.key === Qt.Key_E && event.modifiers & Qt.ControlModifier) {
|
||||
if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
|
||||
} else {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DragManager {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.CrossCursor
|
||||
|
||||
property bool isWindowSelection: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window
|
||||
property var hoveredWindow: root.windows.find(w => {
|
||||
const inCurrentWorkspace = w.workspace.id === HyprlandData.activeWorkspace.id;
|
||||
const withinXRange = w.at[0] <= dragArea.mouseX && dragArea.mouseX <= w.at[0] + w.size[0];
|
||||
const withinYRange = w.at[1] <= dragArea.mouseY && dragArea.mouseY <= w.at[1] + w.size[1];
|
||||
return inCurrentWorkspace && withinXRange && withinYRange;
|
||||
})
|
||||
property int winPadding: 1
|
||||
property int selectionX: isWindowSelection ? ((hoveredWindow?.at[0] ?? 0) - winPadding) : regionTopLeftX
|
||||
property int selectionY: isWindowSelection ? ((hoveredWindow?.at[1] ?? 0) - winPadding) : regionTopLeftY
|
||||
property int selectionWidth: isWindowSelection ? ((hoveredWindow?.size[0] ?? 0) + winPadding * 2) : regionWidth
|
||||
property int selectionHeight: isWindowSelection ? ((hoveredWindow?.size[1] ?? 0) + winPadding * 2) : regionHeight
|
||||
|
||||
onDragReleased: (diffX, diffY) => {
|
||||
if (selectionWidth === 0 || selectionHeight === 0) {
|
||||
return;
|
||||
}
|
||||
const screenshotDir = Config.options.screenSnip.savePath !== "" ? Config.options.screenSnip.savePath : "";
|
||||
const screenshotAction = root.getScreenshotAction();
|
||||
const command = ScreenshotAction.getCommand(dragArea.selectionX * root.monitorScale //
|
||||
, dragArea.selectionY * root.monitorScale //
|
||||
, dragArea.selectionWidth * root.monitorScale//
|
||||
, dragArea.selectionHeight * root.monitorScale //
|
||||
, root.screenshotPath //
|
||||
, screenshotAction //
|
||||
, screenshotDir); // yo wtf is this formatting qmlls do be funnie
|
||||
snipProc.command = command;
|
||||
|
||||
// Image post-processing
|
||||
snipProc.startDetached();
|
||||
root.close();
|
||||
}
|
||||
|
||||
WRectangularSelection {
|
||||
id: rectangularSelection
|
||||
anchors.fill: parent
|
||||
regionX: dragArea.selectionX
|
||||
regionY: dragArea.selectionY
|
||||
regionWidth: dragArea.selectionWidth
|
||||
regionHeight: dragArea.selectionHeight
|
||||
dashed: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect
|
||||
}
|
||||
|
||||
RegionSelectionOptionsToolbar {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component RegionSelectionOptionsToolbar: WToolbar {
|
||||
// Image/video
|
||||
WToolbarTabBar {
|
||||
currentIndex: switch (root.mediaType) {
|
||||
case WRegionSelectionPanel.MediaType.Image:
|
||||
return 0;
|
||||
case WRegionSelectionPanel.MediaType.Video:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
WToolbarIconTabButton {
|
||||
icon.name: "camera"
|
||||
icon.color: Looks.colors.fg
|
||||
}
|
||||
WToolbarIconTabButton {
|
||||
icon.name: "video"
|
||||
icon.color: Looks.colors.fg
|
||||
}
|
||||
onCurrentIndexChanged: {
|
||||
switch (currentIndex) {
|
||||
case 0:
|
||||
root.mediaType = WRegionSelectionPanel.MediaType.Image;
|
||||
break;
|
||||
case 1:
|
||||
root.mediaType = WRegionSelectionPanel.MediaType.Video;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WToolTip {
|
||||
text: Translation.tr("Snip")
|
||||
}
|
||||
}
|
||||
|
||||
// Selection type
|
||||
WToolbarButton {
|
||||
id: selectionTypeBtn
|
||||
implicitWidth: selectionTypeBtnRow.implicitWidth + 11 * 2
|
||||
leftPadding: 11
|
||||
rightPadding: 11
|
||||
onClicked: {
|
||||
selectionTypeMenu.visible = !selectionTypeMenu.visible;
|
||||
}
|
||||
contentItem: Row {
|
||||
id: selectionTypeBtnRow
|
||||
spacing: 4
|
||||
FluentIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon: switch (root.selectionMode) {
|
||||
case WRegionSelectionPanel.SelectionMode.Rect:
|
||||
return "crop";
|
||||
case WRegionSelectionPanel.SelectionMode.Window:
|
||||
return "calendar-add";
|
||||
default:
|
||||
return "crop";
|
||||
}
|
||||
implicitSize: 18
|
||||
}
|
||||
FluentIcon {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: (parent.height - height) / 2 + (selectionTypeBtn.down ? 2 : 0)
|
||||
Behavior on topMargin {
|
||||
animation: Looks.transition.enter.createObject(this)
|
||||
}
|
||||
}
|
||||
icon: "chevron-down"
|
||||
implicitSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
WMenu {
|
||||
id: selectionTypeMenu
|
||||
onClosed: screencopyView.focus = true
|
||||
x: -margins
|
||||
y: -margins - (selectionTypeBtn.parent.height - selectionTypeBtn.height) - 16
|
||||
topMargin: -6
|
||||
height: implicitHeight + sourceEdgeMargin
|
||||
|
||||
color: Looks.colors.bg1Base
|
||||
|
||||
Action {
|
||||
icon.name: "crop"
|
||||
text: Translation.tr("Rectangle")
|
||||
checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect
|
||||
onTriggered: {
|
||||
root.selectionMode = WRegionSelectionPanel.SelectionMode.Rect;
|
||||
}
|
||||
}
|
||||
Action {
|
||||
icon.name: "calendar-add"
|
||||
text: Translation.tr("Window")
|
||||
checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window
|
||||
onTriggered: {
|
||||
root.selectionMode = WRegionSelectionPanel.SelectionMode.Window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WToolTip {
|
||||
text: Translation.tr("Snipping area")
|
||||
}
|
||||
}
|
||||
|
||||
// Markup
|
||||
WToolbarIconButton {
|
||||
icon.name: "image-edit"
|
||||
enabled: root.mediaType === WRegionSelectionPanel.MediaType.Image
|
||||
checked: root.imageAction === WRegionSelectionPanel.ImageAction.Menu
|
||||
onClicked: {
|
||||
if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
|
||||
} else {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Menu;
|
||||
}
|
||||
}
|
||||
WToolTip {
|
||||
text: Translation.tr("Quick markup (Ctrl+E)")
|
||||
}
|
||||
}
|
||||
|
||||
WToolbarSeparator {}
|
||||
|
||||
// Tools
|
||||
WToolbarIconButton {
|
||||
icon.name: "search-visual"
|
||||
checked: root.imageAction === WRegionSelectionPanel.ImageAction.Search
|
||||
onClicked: {
|
||||
if (root.imageAction === WRegionSelectionPanel.ImageAction.Search && root.mediaType === WRegionSelectionPanel.MediaType.Image) {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
|
||||
} else {
|
||||
root.mediaType = WRegionSelectionPanel.MediaType.Image;
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Search;
|
||||
}
|
||||
}
|
||||
WToolTip {
|
||||
text: Translation.tr("Image search")
|
||||
}
|
||||
}
|
||||
WToolbarIconButton {
|
||||
icon.name: "eyedropper"
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["bash", "-c", "sleep 0.2; hyprpicker -a"]);
|
||||
root.closed();
|
||||
}
|
||||
WToolTip {
|
||||
text: Translation.tr("Color picker")
|
||||
}
|
||||
}
|
||||
WToolbarIconButton {
|
||||
icon.name: "scan-text"
|
||||
checked: root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition
|
||||
onClicked: {
|
||||
if (root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition && root.mediaType === WRegionSelectionPanel.MediaType.Image) {
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.Copy;
|
||||
} else {
|
||||
root.mediaType = WRegionSelectionPanel.MediaType.Image;
|
||||
root.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;
|
||||
}
|
||||
}
|
||||
WToolTip {
|
||||
text: Translation.tr("Text extractor")
|
||||
}
|
||||
}
|
||||
|
||||
WToolbarSeparator {}
|
||||
|
||||
WToolbarIconButton {
|
||||
icon.name: "dismiss"
|
||||
onClicked: root.close()
|
||||
WToolTip {
|
||||
text: Translation.tr("Close (Esc)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import qs
|
||||
import qs.modules.common
|
||||
import qs.modules.common.functions
|
||||
import qs.modules.common.widgets
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
function dismiss() {
|
||||
GlobalStates.regionSelectorOpen = false;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: regionSelectorLoader
|
||||
active: GlobalStates.regionSelectorOpen
|
||||
|
||||
sourceComponent: WRegionSelectionPanel {
|
||||
onClosed: root.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
function screenshot() {
|
||||
GlobalStates.regionSelectorOpen = true;
|
||||
}
|
||||
|
||||
function ocr() {
|
||||
GlobalStates.regionSelectorOpen = true;
|
||||
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;
|
||||
regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;
|
||||
}
|
||||
|
||||
function record() {
|
||||
GlobalStates.regionSelectorOpen = true;
|
||||
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;
|
||||
regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.Record;
|
||||
}
|
||||
|
||||
function recordWithSound() {
|
||||
GlobalStates.regionSelectorOpen = true;
|
||||
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;
|
||||
regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.RecordWithSound;
|
||||
}
|
||||
|
||||
function search() {
|
||||
GlobalStates.regionSelectorOpen = true;
|
||||
regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;
|
||||
regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.Search;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "region"
|
||||
|
||||
function screenshot() {
|
||||
root.screenshot();
|
||||
}
|
||||
function ocr() {
|
||||
root.ocr();
|
||||
}
|
||||
function record() {
|
||||
root.record();
|
||||
}
|
||||
function recordWithSound() {
|
||||
root.recordWithSound();
|
||||
}
|
||||
function search() {
|
||||
root.search();
|
||||
}
|
||||
}
|
||||
|
||||
GlobalShortcut {
|
||||
name: "regionScreenshot"
|
||||
description: "Takes a screenshot of the selected region"
|
||||
onPressed: root.screenshot()
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "regionSearch"
|
||||
description: "Searches the selected region"
|
||||
onPressed: root.search()
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "regionOcr"
|
||||
description: "Recognizes text in the selected region"
|
||||
onPressed: root.ocr()
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "regionRecord"
|
||||
description: "Records the selected region"
|
||||
onPressed: root.record()
|
||||
}
|
||||
GlobalShortcut {
|
||||
name: "regionRecordWithSound"
|
||||
description: "Records the selected region with sound"
|
||||
onPressed: root.recordWithSound()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user