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 } 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)") } } } }