Merge branch 'end-4:main' into main

This commit is contained in:
Perdixky
2025-12-04 16:47:56 +08:00
committed by GitHub
29 changed files with 622 additions and 156 deletions
+12
View File
@@ -8,3 +8,15 @@
#env = SDL_IM_MODULE, fcitx #env = SDL_IM_MODULE, fcitx
#env = GLFW_IM_MODULE, ibus #env = GLFW_IM_MODULE, ibus
#env = INPUT_METHOD, fcitx #env = INPUT_METHOD, fcitx
# ######## Wayland #########
# Tearing
# env = WLR_DRM_NO_ATOMIC, 1
# ?
# env = WLR_NO_HARDWARE_CURSORS, 1
# ######## EDITOR #########
#https://wiki.archlinux.org/title/Category:Text_editors
# for example: vi nano nvim ...
#env = EDITOR, vim
+4
View File
@@ -1,2 +1,6 @@
# Put general config stuff here # Put general config stuff here
# Here's a list of every variable: https://wiki.hyprland.org/Configuring/Variables/ # Here's a list of every variable: https://wiki.hyprland.org/Configuring/Variables/
# monitor=,addreserved, 0, 0, 0, 0 # Custom reserved area
# HDMI port: mirror display. To see device name, use `hyprctl monitors`
+13
View File
@@ -4,6 +4,19 @@
bind = Ctrl+Super, Slash, exec, xdg-open ~/.config/illogical-impulse/config.json # Edit shell config bind = Ctrl+Super, Slash, exec, xdg-open ~/.config/illogical-impulse/config.json # Edit shell config
bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit extra keybinds bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit extra keybinds
##! Apps
# bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "${TERMINAL}" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal
# bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "${TERMINAL}" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (alt)
# bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "${TERMINAL}" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (for Ubuntu people)
# bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "${TERMINAL}" "kitty -1 fish -c yazi" # File manager
# bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "google-chrome-stable" "zen-browser" "firefox" "brave" "chromium" "microsoft-edge-stable" "opera" "librewolf" # Browser
# bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "cursor" "zed" "zedit" "zeditor" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" "command -v micro && kitty -1 micro" # Code editor
# bind = Ctrl+Super+Shift+Alt, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" "libreoffice" # Office software
# bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor
# bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer
# bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "qs -p ~/.config/quickshell/$qsConfig/settings.qml" "systemsettings" "gnome-control-center" "better-control" # Settings app
# bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # Task manager
# Add stuff here # Add stuff here
# Use #! to add an extra column on the cheatsheet # Use #! to add an extra column on the cheatsheet
# Use ##! to add a section in that column # Use ##! to add a section in that column
+8
View File
@@ -1,3 +1,11 @@
# You can put custom rules here # You can put custom rules here
# Window/layer rules: https://wiki.hyprland.org/Configuring/Window-Rules/ # Window/layer rules: https://wiki.hyprland.org/Configuring/Window-Rules/
# Workspace rules: https://wiki.hyprland.org/Configuring/Workspace-Rules/ # Workspace rules: https://wiki.hyprland.org/Configuring/Workspace-Rules/
# ######## Window rules ########
# Uncomment to apply global transparency to all windows:
# windowrulev2 = opacity 0.89 override 0.89 override, class:.*
# Disable blur for all xwayland apps
# windowrulev2 = noblur, xwayland:1
-6
View File
@@ -9,12 +9,6 @@ env = QT_QPA_PLATFORM, wayland
env = QT_QPA_PLATFORMTHEME, kde env = QT_QPA_PLATFORMTHEME, kde
env = XDG_MENU_PREFIX, plasma- env = XDG_MENU_PREFIX, plasma-
# ######## Wayland #########
# Tearing
# env = WLR_DRM_NO_ATOMIC, 1
# ?
# env = WLR_NO_HARDWARE_CURSORS, 1
# ######## Virtual envrionment ######### # ######## Virtual envrionment #########
env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv
-4
View File
@@ -1,9 +1,5 @@
# MONITOR CONFIG # MONITOR CONFIG
monitor=,preferred,auto,1 monitor=,preferred,auto,1
# monitor=,addreserved, 0, 0, 0, 0 # Custom reserved area
# HDMI port: mirror display. To see device name, use `hyprctl monitors`
# monitor=HDMI-A-1,1920x1080@60,1920x0,1,mirror,eDP-1
gesture = 3, swipe, move, gesture = 3, swipe, move,
gesture = 3, pinch, float gesture = 3, pinch, float
+1 -5
View File
@@ -1,12 +1,8 @@
# ######## Window rules ######## # ######## Window rules ########
# Uncomment to apply global transparency to all windows:
# windowrulev2 = opacity 0.89 override 0.89 override, class:.*
# Disable blur for xwayland context menus # Disable blur for xwayland context menus
windowrulev2 = noblur,class:^()$,title:^()$ windowrulev2 = noblur,class:^()$,title:^()$
# Disable blur for all xwayland apps
# windowrulev2 = noblur, xwayland:1
# Disable blur for every window # Disable blur for every window
windowrulev2 = noblur, class:.* windowrulev2 = noblur, class:.*
@@ -590,8 +590,9 @@ Singleton {
// false will make (some) stuff also be like that for accuracy. // false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button // Example: the right-click menu of the Start button
property JsonObject tweaks: JsonObject { property JsonObject tweaks: JsonObject {
property bool smootherMenuAnimations: true
property bool switchHandlePositionFix: true property bool switchHandlePositionFix: true
property bool smootherMenuAnimations: true
property bool smootherSearchBar: true
} }
property JsonObject bar: JsonObject { property JsonObject bar: JsonObject {
property bool bottom: true property bool bottom: true
@@ -0,0 +1,29 @@
import QtQuick
import Quickshell
QtObject {
enum IconType { Material, Text, System, None }
enum FontType { Normal, Monospace }
// General stuff
property string type: ""
property var fontType: LauncherSearchResult.FontType.Normal
property string name: ""
property string rawValue: ""
property string iconName: ""
property var iconType: LauncherSearchResult.IconType.None
property string verb: ""
property bool blurImage: false
property var execute: () => {
print("Not implemented");
}
property var actions: []
// Stuff needed for DesktopEntry objects
property bool shown: true
property string comment: ""
property bool runInTerminal: false
property string genericName: ""
property list<string> keywords: []
}
@@ -278,7 +278,7 @@ Item {
StyledToolTip { StyledToolTip {
extraVisibleCondition: false extraVisibleCondition: false
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
text: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}` text: `${windowData?.title}\n[${windowData?.class}] ${windowData?.xwayland ? "[XWayland] " : ""}`
} }
} }
} }
@@ -2,6 +2,7 @@
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
import QtQuick import QtQuick
@@ -12,20 +13,27 @@ import Quickshell.Hyprland
RippleButton { RippleButton {
id: root id: root
property var entry property LauncherSearchResult entry
property string query property string query
property bool entryShown: entry?.shown ?? true property bool entryShown: entry?.shown ?? true
property string itemType: entry?.type ?? Translation.tr("App") property string itemType: entry?.type ?? Translation.tr("App")
property string itemName: entry?.name ?? "" property string itemName: entry?.name ?? ""
property string itemIcon: entry?.icon ?? "" property var iconType: entry?.iconType
property string iconName: entry?.iconName ?? ""
property var itemExecute: entry?.execute property var itemExecute: entry?.execute
property string fontType: entry?.fontType ?? "main" property var fontType: switch(entry?.fontType) {
property string itemClickActionName: entry?.clickActionName ?? "Open" case LauncherSearchResult.FontType.Monospace:
property string bigText: entry?.bigText ?? "" return "monospace"
property string materialSymbol: entry?.materialSymbol ?? "" case LauncherSearchResult.FontType.Normal:
property string cliphistRawString: entry?.cliphistRawString ?? "" return "main"
default:
return "main"
}
property string itemClickActionName: entry?.verb ?? "Open"
property string bigText: entry?.iconType === LauncherSearchResult.IconType.Text ? entry?.iconName ?? "" : ""
property string materialSymbol: entry.iconType === LauncherSearchResult.IconType.Material ? entry?.iconName ?? "" : ""
property string cliphistRawString: entry?.rawValue ?? ""
property bool blurImage: entry?.blurImage ?? false property bool blurImage: entry?.blurImage ?? false
property string blurImageText: entry?.blurImageText ?? "Image hidden"
visible: root.entryShown visible: root.entryShown
property int horizontalMargin: 10 property int horizontalMargin: 10
@@ -97,7 +105,7 @@ RippleButton {
} }
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
if (event.key === Qt.Key_Delete && event.modifiers === Qt.ShiftModifier) { if (event.key === Qt.Key_Delete && event.modifiers === Qt.ShiftModifier) {
const deleteAction = root.entry.actions.find(action => action.name == "Delete"); const deleteAction = root.entry.actions.find(action => action.name == Translation.tr("Delete"));
if (deleteAction) { if (deleteAction) {
deleteAction.execute() deleteAction.execute()
@@ -126,16 +134,24 @@ RippleButton {
Loader { Loader {
id: iconLoader id: iconLoader
active: true active: true
sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : sourceComponent: switch(root.iconType) {
root.bigText ? bigTextComponent : case LauncherSearchResult.IconType.Material:
root.itemIcon !== "" ? iconImageComponent : return materialSymbolComponent
null case LauncherSearchResult.IconType.Text:
return bigTextComponent
case LauncherSearchResult.IconType.System:
return iconImageComponent
case LauncherSearchResult.IconType.None:
return null
default:
return null
}
} }
Component { Component {
id: iconImageComponent id: iconImageComponent
IconImage { IconImage {
source: Quickshell.iconPath(root.itemIcon, "image-missing") source: Quickshell.iconPath(root.iconName, "image-missing")
width: 35 width: 35
height: 35 height: 35
} }
@@ -217,7 +233,6 @@ RippleButton {
maxWidth: contentColumn.width maxWidth: contentColumn.width
maxHeight: 140 maxHeight: 140
blur: root.blurImage blur: root.blurImage
blurText: root.blurImageText
} }
} }
} }
@@ -243,8 +258,8 @@ RippleButton {
delegate: RippleButton { delegate: RippleButton {
id: actionButton id: actionButton
required property var modelData required property var modelData
property string iconName: modelData.icon ?? "" property var iconType: modelData.iconType
property string materialIconName: modelData.materialIcon ?? "" property string iconName: modelData.iconName ?? ""
implicitHeight: 34 implicitHeight: 34
implicitWidth: 34 implicitWidth: 34
@@ -256,16 +271,16 @@ RippleButton {
anchors.centerIn: parent anchors.centerIn: parent
Loader { Loader {
anchors.centerIn: parent anchors.centerIn: parent
active: !(actionButton.iconName !== "") || actionButton.materialIconName active: actionButton.iconType === LauncherSearchResult.IconType.Material || actionButton.iconName === ""
sourceComponent: MaterialSymbol { sourceComponent: MaterialSymbol {
text: actionButton.materialIconName || "video_settings" text: actionButton.iconName || "video_settings"
font.pixelSize: Appearance.font.pixelSize.hugeass font.pixelSize: Appearance.font.pixelSize.hugeass
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
} }
} }
Loader { Loader {
anchors.centerIn: parent anchors.centerIn: parent
active: actionButton.materialIconName.length == 0 && actionButton.iconName && actionButton.iconName !== "" active: actionButton.iconType === LauncherSearchResult.IconType.System && actionButton.iconName !== ""
sourceComponent: IconImage { sourceComponent: IconImage {
source: Quickshell.iconPath(actionButton.iconName) source: Quickshell.iconPath(actionButton.iconName)
implicitSize: 20 implicitSize: 20
@@ -42,12 +42,6 @@ Item { // Wrapper
LauncherSearch.query = text; LauncherSearch.query = text;
} }
function containsUnsafeLink(entry) {
if (entry == undefined) return false;
const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords;
return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords);
}
Keys.onPressed: event => { Keys.onPressed: event => {
// Prevent Esc and Backspace from registering // Prevent Esc and Backspace from registering
if (event.key === Qt.Key_Escape) if (event.key === Qt.Key_Escape)
@@ -64,7 +64,7 @@ Singleton {
property color bg0: "#1C1C1C" property color bg0: "#1C1C1C"
property color bg0Border: "#404040" property color bg0Border: "#404040"
property color bg1Base: "#2C2C2C" property color bg1Base: "#2C2C2C"
property color bg1: "#a8a8a8" property color bg1: '#9f9f9f'
property color bg1Hover: "#b3b3b3" property color bg1Hover: "#b3b3b3"
property color bg1Active: '#727272' property color bg1Active: '#727272'
property color bg1Border: '#bebebe' property color bg1Border: '#bebebe'
@@ -125,6 +125,7 @@ Singleton {
property color accentUnfocused: root.dark ? root.darkColors.accentUnfocused : root.lightColors.accentUnfocused property color accentUnfocused: root.dark ? root.darkColors.accentUnfocused : root.lightColors.accentUnfocused
property color accentFg: ColorUtils.isDark(accent) ? "#FFFFFF" : "#000000" property color accentFg: ColorUtils.isDark(accent) ? "#FFFFFF" : "#000000"
property color selection: Appearance.colors.colPrimaryContainer property color selection: Appearance.colors.colPrimaryContainer
property color selectionFg: Appearance.colors.colOnPrimaryContainer
} }
radius: QtObject { radius: QtObject {
@@ -44,6 +44,7 @@ WButton {
radius: Looks.radius.medium radius: Looks.radius.medium
color: root.color color: root.color
Behavior on color { Behavior on color {
enabled: root.animateChoiceHighlight
animation: Looks.transition.color.createObject(this) animation: Looks.transition.color.createObject(this)
} }
@@ -14,8 +14,9 @@ Menu {
property bool downDirection: false property bool downDirection: false
property bool hasIcons: false // TODO: implement property bool hasIcons: false // TODO: implement
implicitWidth: background.implicitWidth + root.padding * 2 implicitWidth: background.implicitWidth + margins * 2
implicitHeight: background.implicitHeight + root.padding * 2 implicitHeight: background.implicitHeight + margins * 2
margins: 10
padding: 3 padding: 3
property real sourceEdgeMargin: -implicitHeight property real sourceEdgeMargin: -implicitHeight
clip: true clip: true
@@ -24,7 +25,7 @@ Menu {
NumberAnimation { NumberAnimation {
property: "sourceEdgeMargin" property: "sourceEdgeMargin"
from: -root.implicitHeight from: -root.implicitHeight
to: root.padding to: root.margins
duration: 200 duration: 200
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn
@@ -33,7 +34,7 @@ Menu {
exit: Transition { exit: Transition {
NumberAnimation { NumberAnimation {
property: "sourceEdgeMargin" property: "sourceEdgeMargin"
from: root.padding from: root.margins
to: -root.implicitHeight to: -root.implicitHeight
duration: 150 duration: 150
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -41,43 +42,55 @@ Menu {
} }
} }
background: WPane { background: Item {
anchors { id: bgItem
left: parent.left implicitWidth: bgPane.implicitWidth
right: parent.right implicitHeight: bgPane.implicitHeight
top: root.downDirection ? parent.top : undefined WPane {
bottom: root.downDirection ? undefined : parent.bottom id: bgPane
margins: root.padding anchors {
topMargin: root.downDirection ? root.sourceEdgeMargin : root.padding left: parent.left
bottomMargin: root.downDirection ? root.padding : root.sourceEdgeMargin right: parent.right
} top: root.downDirection ? parent.top : undefined
contentItem: Rectangle { bottom: root.downDirection ? undefined : parent.bottom
color: Looks.colors.bg1Base margins: root.margins
implicitWidth: menuListView.implicitWidth + root.padding * 2 topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins
implicitHeight: root.contentItem.implicitHeight + root.padding * 2 bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin
}
contentItem: Rectangle {
color: Looks.colors.bg1Base
implicitWidth: menuListView.implicitWidth + root.padding * 2
implicitHeight: root.contentItem.implicitHeight + root.padding * 2
}
} }
} }
contentItem: ListView { contentItem: Item {
id: menuListView implicitWidth: menuListView.implicitWidth
anchors { implicitHeight: menuListView.implicitHeight
left: parent.left ListView {
right: parent.right id: menuListView
top: root.downDirection ? parent.top : undefined anchors {
bottom: root.downDirection ? undefined : parent.bottom left: parent.left
margins: root.padding * 2 right: parent.right
topMargin: root.downDirection ? root.sourceEdgeMargin : root.padding top: root.downDirection ? parent.top : undefined
bottomMargin: root.downDirection ? root.padding : root.sourceEdgeMargin bottom: root.downDirection ? undefined : parent.bottom
} margins: root.margins // ????
implicitHeight: contentHeight topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins
implicitWidth: Array.from({ bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin
length: count }
}, (_, i) => itemAtIndex(i)?.implicitWidth ?? 0).reduce((a, b) => a > b ? a : b) implicitHeight: contentHeight
implicitWidth: Array.from({
length: count
}, (_, i) => itemAtIndex(i)?.implicitWidth ?? 0).reduce((a, b) => a > b ? a : b)
model: root.contentModel model: root.contentModel
}
} }
delegate: WMenuItem { delegate: WMenuItem {
id: menuItemDelegate id: menuItemDelegate
width: ListView.view?.width
} }
} }
@@ -10,14 +10,17 @@ WButton {
id: root id: root
property alias iconName: iconContent.icon property alias iconName: iconContent.icon
property alias iconSize: iconContent.implicitSize
property alias monochrome: iconContent.monochrome property alias monochrome: iconContent.monochrome
implicitWidth: 40 implicitWidth: 40
implicitHeight: 40 implicitHeight: 40
contentItem: FluentIcon { contentItem: Item {
id: iconContent FluentIcon {
anchors.centerIn: parent id: iconContent
implicitSize: 18 anchors.centerIn: parent
icon: root.iconName implicitSize: 18
icon: root.iconName
}
} }
} }
@@ -15,4 +15,5 @@ TextInput {
} }
selectionColor: Looks.colors.selection selectionColor: Looks.colors.selection
selectedTextColor: Looks.colors.selectionFg
} }
@@ -12,26 +12,36 @@ import qs.modules.waffle.looks
FooterRectangle { FooterRectangle {
id: root id: root
property real horizontalPadding: 32
property real verticalPadding: 16
property bool searching: text.length > 0 property bool searching: text.length > 0
property alias text: searchInput.text property alias text: searchInput.text
implicitHeight: outline.implicitHeight + verticalPadding * 2
Component.onCompleted: searchInput.forceActiveFocus() Component.onCompleted: searchInput.forceActiveFocus()
focus: true focus: true
color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter
implicitWidth: 832 // TODO: Make sizes naturally inferred Behavior on horizontalPadding {
implicitHeight: 63 enabled: Config.options.waffles.tweaks.smootherSearchBar
animation: Looks.transition.move.createObject(this)
}
Behavior on verticalPadding {
enabled: Config.options.waffles.tweaks.smootherSearchBar
animation: Looks.transition.move.createObject(this)
}
Rectangle { Rectangle {
id: outline id: outline
anchors { anchors {
fill: parent left: parent.left
leftMargin: 32 right: parent.right
rightMargin: 32 leftMargin: root.horizontalPadding
topMargin: 16 rightMargin: root.horizontalPadding
bottomMargin: 15 verticalCenter: parent.verticalCenter
} }
implicitHeight: 32
color: "transparent" color: "transparent"
radius: height / 2 radius: height / 2
border.width: 1 border.width: 1
@@ -12,5 +12,25 @@ import qs.modules.waffle.looks
BodyRectangle { BodyRectangle {
id: root id: root
property string searchText: LauncherSearch.query
ColumnLayout {
anchors {
fill: parent
topMargin: 2
leftMargin: 24
rightMargin: 24
}
spacing: 12
TagStrip {
Layout.fillWidth: true
Layout.fillHeight: false
}
SearchResults {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
} }
@@ -0,0 +1,60 @@
import qs.modules.waffle.looks
import qs.modules.common.functions
import qs.modules.common
import qs.services
import qs
import Quickshell
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick
pragma ComponentBehavior: Bound
RowLayout {
id: root
function focusFirstItem() {
resultList.currentIndex = 0;
}
ResultList {
id: resultList
Layout.fillHeight: true
Layout.fillWidth: true
}
ResultPreview {
Layout.preferredWidth: 386
Layout.leftMargin: 1
Layout.rightMargin: 1
}
component ResultList: ListView {
section {
criteria: ViewSection.FullString
property: "type"
}
clip: true
spacing: 4
model: ScriptModel {
values: {
// TODO: categorize and have max per category
LauncherSearch.results.slice(0, 10)
}
onValuesChanged: {
root.focusFirstItem();
}
}
delegate: WSearchResultButton {
required property int index
required property var modelData
entry: modelData
firstEntry: index === 0
width: ListView.view?.width
}
}
component ResultPreview: Rectangle {
Layout.fillHeight: true
color: Looks.colors.bg1
radius: Looks.radius.large
}
}
@@ -14,26 +14,43 @@ WBarAttachedPanelContent {
id: root id: root
property bool searching: false property bool searching: false
property string searchText: "" property string searchText: LauncherSearch.query
contentItem: WPane { contentItem: WPane {
contentItem: WPanelPageColumn { contentItem: WPanelPageColumn {
SearchBar { SearchBar {
focus: true focus: true
Layout.fillWidth: true Layout.fillWidth: true
implicitWidth: 832 // TODO: Make sizes naturally inferred
horizontalPadding: root.searching ? 24 : 32
// verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel
Synchronizer on searching { Synchronizer on searching {
property alias target: root.searching property alias target: root.searching
} }
Synchronizer on text { text: root.searchText
property alias source: root.searchText onTextChanged: {
LauncherSearch.query = text;
} }
} }
Loader { Item {
id: pageContentLoader implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred
Layout.fillWidth: true Layout.fillWidth: true
source: root.searching ? "SearchPageContent.qml" : "StartPageContent.qml" Loader {
id: pageContentLoader
anchors.fill: parent
sourceComponent: root.searching ? searchPageComp : startPageComp
}
} }
} }
} }
Component {
id: searchPageComp
SearchPageContent {}
}
Component {
id: startPageComp
StartPageContent {}
}
} }
@@ -17,7 +17,7 @@ WPanelPageColumn {
WPanelSeparator {} WPanelSeparator {}
BodyRectangle { BodyRectangle {
implicitHeight: 736 // TODO: Make sizes naturally inferred Layout.fillHeight: true
} }
WPanelSeparator {} WPanelSeparator {}
@@ -0,0 +1,92 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
import qs.modules.waffle.looks
RowLayout {
WPanelIconButton {
implicitWidth: 36
implicitHeight: 36
iconSize: 24
iconName: "arrow-left"
onClicked: LauncherSearch.query = ""
}
ListView {
id: tagListView
Layout.fillWidth: true
Layout.fillHeight: true
orientation: Qt.Horizontal
spacing: 4
model: [
{
name: Translation.tr("All"),
prefix: ""
},
{
name: Translation.tr("Apps"),
prefix: Config.options.search.prefix.app
},
{
name: Translation.tr("Actions"),
prefix: Config.options.search.prefix.action
},
{
name: Translation.tr("Clipboard"),
prefix: Config.options.search.prefix.clipboard
},
{
name: Translation.tr("Emojis"),
prefix: Config.options.search.prefix.emojis
},
{
name: Translation.tr("Math"),
prefix: Config.options.search.prefix.math
},
{
name: Translation.tr("Commands"),
prefix: Config.options.search.prefix.shellCommand
},
{
name: Translation.tr("Web"),
prefix: Config.options.search.prefix.webSearch
},
]
delegate: WBorderedButton {
id: tagButton
required property var modelData
border.width: 1
radius: height / 2
implicitWidth: tagButtonText.implicitWidth + 12 * 2
implicitHeight: 32
checked: {
if (modelData.prefix != "") {
return LauncherSearch.query.startsWith(modelData.prefix);
} else {
return !tagListView.model.some(i => (i.prefix != "" && LauncherSearch.query.startsWith(i.prefix)))
}
}
contentItem: Item {
WText {
id: tagButtonText
anchors.centerIn: parent
color: tagButton.fgColor
text: tagButton.modelData.name
font.pixelSize: Looks.font.pixelSize.large
}
}
onClicked: LauncherSearch.ensurePrefix(tagButton.modelData.prefix)
}
}
WPanelIconButton {
implicitWidth: 36
implicitHeight: 36
iconSize: 24
iconName: "more-horizontal"
}
}
@@ -0,0 +1,81 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.modules.waffle.looks
WChoiceButton {
id: root
required property LauncherSearchResult entry
property bool firstEntry: false
checked: focus
animateChoiceHighlight: false
implicitWidth: contentLayout.implicitWidth + leftPadding + rightPadding
implicitHeight: contentLayout.implicitHeight + topPadding + bottomPadding
onClicked: {
GlobalStates.searchOpen = false
root.entry.execute()
}
contentItem: RowLayout {
id: contentLayout
spacing: 8
EntryIcon {}
EntryNameColumn {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
}
component EntryIcon: Item {
implicitWidth: 24
implicitHeight: 24
Loader {
anchors.centerIn: parent
active: root.entry.iconType === LauncherSearchResult.IconType.System
sourceComponent: WAppIcon {
implicitSize: 24
tryCustomIcon: false
iconName: root.entry.iconName
}
}
Loader {
anchors.centerIn: parent
active: root.entry.iconType === LauncherSearchResult.IconType.Text
sourceComponent: WText {
text: root.entry.iconName
font.pixelSize: 24
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
component EntryNameColumn: ColumnLayout {
spacing: 4
WText {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: root.entry.name
font.pixelSize: Looks.font.pixelSize.large
maximumLineCount: 2
}
WText {
Layout.fillWidth: true
visible: root.firstEntry
text: root.entry.type
color: Looks.colors.accentUnfocused
}
}
}
@@ -4,7 +4,6 @@ import qs.modules.common
import qs.modules.common.models import qs.modules.common.models
import qs.modules.common.functions import qs.modules.common.functions
import QtQuick import QtQuick
import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -12,6 +11,15 @@ Singleton {
id: root id: root
property string query: "" property string query: ""
function ensurePrefix(prefix) {
if ([Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch,].some(i => root.query.startsWith(i))) {
root.query = prefix + root.query.slice(1);
} else {
root.query = prefix + root.query;
}
}
property var searchActions: [ property var searchActions: [
{ {
action: "accentcolor", action: "accentcolor",
@@ -74,12 +82,13 @@ Singleton {
property string mathResult: "" property string mathResult: ""
property bool clipboardWorkSafetyActive: { property bool clipboardWorkSafetyActive: {
const enabled = Config.options.workSafety.enable.clipboard; const enabled = Config.options.workSafety.enable.clipboard;
const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)) const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords));
return enabled && sensitiveNetwork; return enabled && sensitiveNetwork;
} }
function containsUnsafeLink(entry) { function containsUnsafeLink(entry) {
if (entry == undefined) return false; if (entry == undefined)
return false;
const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords; const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords;
return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords); return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords);
} }
@@ -128,95 +137,121 @@ Singleton {
shouldBlurImage = shouldBlurImage && (root.containsUnsafeLink(array[index - 1]) || root.containsUnsafeLink(array[index + 1])); shouldBlurImage = shouldBlurImage && (root.containsUnsafeLink(array[index - 1]) || root.containsUnsafeLink(array[index + 1]));
} }
const type = `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`; const type = `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`;
return { return resultComp.createObject(null, {
key: type, rawValue: entry,
cliphistRawString: entry,
name: StringUtils.cleanCliphistEntry(entry), name: StringUtils.cleanCliphistEntry(entry),
clickActionName: "", verb: "",
type: type, type: type,
execute: () => { execute: () => {
Cliphist.copy(entry); Cliphist.copy(entry);
}, },
actions: [ actions: [resultComp.createObject(null, {
{ name: Translation.tr("Copy"),
name: "Copy", iconName: "content_copy",
materialIcon: "content_copy", iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
Cliphist.copy(entry); Cliphist.copy(entry);
} }
}, }), resultComp.createObject(null, {
{ name: Translation.tr("Delete"),
name: "Delete", iconName: "delete",
materialIcon: "delete", iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
Cliphist.deleteEntry(entry); Cliphist.deleteEntry(entry);
} }
} })],
], blurImage: shouldBlurImage
blurImage: shouldBlurImage, });
blurImageText: Translation.tr("Work safety")
};
}).filter(Boolean); }).filter(Boolean);
} else if (root.query.startsWith(Config.options.search.prefix.emojis)) { } else if (root.query.startsWith(Config.options.search.prefix.emojis)) {
// Clipboard // Clipboard
const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.emojis); const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.emojis);
return Emojis.fuzzyQuery(searchString).map(entry => { return Emojis.fuzzyQuery(searchString).map(entry => {
const emoji = entry.match(/^\s*(\S+)/)?.[1] || ""; const emoji = entry.match(/^\s*(\S+)/)?.[1] || "";
return { return resultComp.createObject(null, {
key: emoji, rawValue: entry,
cliphistRawString: entry,
bigText: emoji,
name: entry.replace(/^\s*\S+\s+/, ""), name: entry.replace(/^\s*\S+\s+/, ""),
clickActionName: "", iconName: emoji,
type: "Emoji", iconType: LauncherSearchResult.IconType.Text,
verb: Translation.tr("Copy"),
type: Translation.tr("Emoji"),
execute: () => { execute: () => {
Quickshell.clipboardText = entry.match(/^\s*(\S+)/)?.[1]; Quickshell.clipboardText = entry.match(/^\s*(\S+)/)?.[1];
} }
}; });
}).filter(Boolean); }).filter(Boolean);
} }
////////////////// Init /////////////////// ////////////////// Init ///////////////////
nonAppResultsTimer.restart(); nonAppResultsTimer.restart();
const mathResultObject = { const mathResultObject = resultComp.createObject(null, {
key: `Math result: ${root.mathResult}`,
name: root.mathResult, name: root.mathResult,
clickActionName: Translation.tr("Copy"), verb: Translation.tr("Copy"),
type: Translation.tr("Math result"), type: Translation.tr("Math result"),
fontType: "monospace", fontType: LauncherSearchResult.FontType.Monospace,
materialSymbol: 'calculate', iconName: 'calculate',
iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
Quickshell.clipboardText = root.mathResult; Quickshell.clipboardText = root.mathResult;
} }
};
const appResultObjects = AppSearch.fuzzyQuery(StringUtils.cleanPrefix(root.query, Config.options.search.prefix.app)).map(entry => {
entry.clickActionName = Translation.tr("Launch");
entry.type = Translation.tr("App");
entry.key = entry.execute;
return entry;
}); });
const commandResultObject = { const appResultObjects = AppSearch.fuzzyQuery(StringUtils.cleanPrefix(root.query, Config.options.search.prefix.app)).map(entry => {
key: `cmd ${root.query}`, return resultComp.createObject(null, {
type: Translation.tr("App"),
name: entry.name,
iconName: entry.icon,
iconType: LauncherSearchResult.IconType.System,
verb: Translation.tr("Launch"),
execute: () => {
if (!entry.runInTerminal)
entry.execute();
else {
// Probably needs more proper escaping, but this will do for now
Quickshell.execDetached(["bash", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(entry.command.join(' '))}'`]);
}
},
comment: entry.comment,
runInTerminal: entry.runInTerminal,
genericName: entry.genericName,
keywords: entry.keywords,
actions: entry.actions.map(action => {
return resultComp.createObject(null, {
name: action.name,
iconName: action.icon,
iconType: LauncherSearchResult.IconType.System,
execute: () => {
if (!action.runInTerminal)
action.execute();
else {
Quickshell.execDetached(["bash", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(action.command.join(' '))}'`]);
}
}
});
})
});
});
const commandResultObject = resultComp.createObject(null, {
name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.shellCommand).replace("file://", ""), name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.shellCommand).replace("file://", ""),
clickActionName: Translation.tr("Run"), verb: Translation.tr("Run"),
type: Translation.tr("Run command"), type: Translation.tr("Run command"),
fontType: "monospace", fontType: LauncherSearchResult.FontType.Monospace,
materialSymbol: 'terminal', iconName: 'terminal',
iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
let cleanedCommand = root.query.replace("file://", ""); let cleanedCommand = root.query.replace("file://", "");
cleanedCommand = StringUtils.cleanPrefix(cleanedCommand, Config.options.search.prefix.shellCommand); cleanedCommand = StringUtils.cleanPrefix(cleanedCommand, Config.options.search.prefix.shellCommand);
if (cleanedCommand.startsWith(Config.options.search.prefix.shellCommand)) { if (cleanedCommand.startsWith(Config.options.search.prefix.shellCommand)) {
cleanedCommand = cleanedCommand.slice(Config.options.search.prefix.shellCommand.length); cleanedCommand = cleanedCommand.slice(Config.options.search.prefix.shellCommand.length);
} }
Quickshell.execDetached(["bash", "-c", searchingText.startsWith('sudo') ? `${Config.options.apps.terminal} fish -C '${cleanedCommand}'` : cleanedCommand]); Quickshell.execDetached(["bash", "-c", root.query.startsWith('sudo') ? `${Config.options.apps.terminal} fish -C '${cleanedCommand}'` : cleanedCommand]);
} }
}; });
const webSearchResultObject = { const webSearchResultObject = resultComp.createObject(null, {
key: `website ${root.query}`,
name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch), name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch),
clickActionName: Translation.tr("Search"), verb: Translation.tr("Search"),
type: Translation.tr("Search the web"), type: Translation.tr("Search the web"),
materialSymbol: 'travel_explore', iconName: 'travel_explore',
iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
let query = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch); let query = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch);
let url = Config.options.search.engineBaseUrl + query; let url = Config.options.search.engineBaseUrl + query;
@@ -225,20 +260,20 @@ Singleton {
} }
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
} }
}; });
const launcherActionObjects = root.searchActions.map(action => { const launcherActionObjects = root.searchActions.map(action => {
const actionString = `${Config.options.search.prefix.action}${action.action}`; const actionString = `${Config.options.search.prefix.action}${action.action}`;
if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) { if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) {
return { return resultComp.createObject(null, {
key: `Action ${actionString}`,
name: root.query.startsWith(actionString) ? root.query : actionString, name: root.query.startsWith(actionString) ? root.query : actionString,
clickActionName: Translation.tr("Run"), verb: Translation.tr("Run"),
type: Translation.tr("Action"), type: Translation.tr("Action"),
materialSymbol: 'settings_suggest', iconName: 'settings_suggest',
iconType: LauncherSearchResult.IconType.Material,
execute: () => { execute: () => {
action.execute(root.query.split(" ").slice(1).join(" ")); action.execute(root.query.split(" ").slice(1).join(" "));
} }
}; });
} }
return null; return null;
}).filter(Boolean); }).filter(Boolean);
@@ -275,4 +310,9 @@ Singleton {
return result; return result;
} }
Component {
id: resultComp
LauncherSearchResult {}
}
} }
@@ -102,6 +102,10 @@ Singleton {
Process { Process {
id: findCpuMaxFreqProc id: findCpuMaxFreqProc
environment: ({
LANG: "C",
LC_ALL: "C"
})
command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"] command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"]
running: true running: true
stdout: StdioCollector { stdout: StdioCollector {
+5
View File
@@ -43,6 +43,11 @@ if ! command -v pacman >/dev/null 2>&1; then
exit 1 exit 1
fi fi
# Keep makepkg from resetting sudo credentials
if [[ -z "${PACMAN_AUTH:-}" ]]; then
export PACMAN_AUTH="sudo"
fi
showfun remove_deprecated_dependencies showfun remove_deprecated_dependencies
v remove_deprecated_dependencies v remove_deprecated_dependencies
+44
View File
@@ -80,6 +80,50 @@ function prevent_sudo_or_root(){
root) echo -e "${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}";exit 1;; root) echo -e "${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}";exit 1;;
esac esac
} }
# Initialize sudo session and keep it alive in background
# Store PID in a global variable that can be accessed by trap
declare -g SUDO_KEEPALIVE_PID=""
function sudo_init_keepalive(){
# Check if sudo is available
if ! command -v sudo >/dev/null 2>&1; then
return 0
fi
# Skip if already initialized
if [[ -n "$SUDO_KEEPALIVE_PID" ]] && kill -0 "$SUDO_KEEPALIVE_PID" 2>/dev/null; then
return 0
fi
# Prompt for sudo password once at the beginning
echo -e "${STY_CYAN}[$0]: Requesting sudo privileges for installation...${STY_RST}"
if ! sudo -v; then
echo -e "${STY_RED}[$0]: Failed to obtain sudo privileges. Aborting...${STY_RST}"
exit 1
fi
# Start background process to keep sudo session alive
# This updates the sudo timestamp every 60 seconds
(
while true; do
sleep 60
sudo -v 2>/dev/null || exit 0
done
) &
SUDO_KEEPALIVE_PID=$!
echo -e "${STY_GREEN}[$0]: Sudo session initialized and will be kept alive (PID: $SUDO_KEEPALIVE_PID)${STY_RST}"
}
# Stop the sudo keepalive background process
function sudo_stop_keepalive(){
if [[ -n "$SUDO_KEEPALIVE_PID" ]] && kill -0 "$SUDO_KEEPALIVE_PID" 2>/dev/null; then
kill "$SUDO_KEEPALIVE_PID" 2>/dev/null
wait "$SUDO_KEEPALIVE_PID" 2>/dev/null
SUDO_KEEPALIVE_PID=""
fi
}
function git_auto_unshallow(){ function git_auto_unshallow(){
# We need this function for latest_commit_hash to work properly # We need this function for latest_commit_hash to work properly
if [[ -f "$(git rev-parse --git-dir)/shallow" ]]; then if [[ -f "$(git rev-parse --git-dir)/shallow" ]]; then
+12
View File
@@ -71,6 +71,10 @@ case ${SUBCMD_NAME} in
$function $function
done done
pause pause
# Initialize sudo keepalive for the entire install process
sudo_init_keepalive
# Set trap to cleanup when this subcommand exits
trap sudo_stop_keepalive EXIT INT TERM
if [[ "${SKIP_ALLGREETING}" != true ]]; then if [[ "${SKIP_ALLGREETING}" != true ]]; then
source ${SUBCMD_DIR}/0.greeting.sh source ${SUBCMD_DIR}/0.greeting.sh
fi fi
@@ -89,6 +93,10 @@ case ${SUBCMD_NAME} in
$function $function
done done
pause pause
# Initialize sudo keepalive for dependency installation
sudo_init_keepalive
# Set trap to cleanup when this subcommand exits
trap sudo_stop_keepalive EXIT INT TERM
if [[ "${SKIP_ALLDEPS}" != true ]]; then if [[ "${SKIP_ALLDEPS}" != true ]]; then
source ${SUBCMD_DIR}/1.deps-router.sh source ${SUBCMD_DIR}/1.deps-router.sh
fi fi
@@ -98,6 +106,10 @@ case ${SUBCMD_NAME} in
$function $function
done done
pause pause
# Initialize sudo keepalive for setup steps
sudo_init_keepalive
# Set trap to cleanup when this subcommand exits
trap sudo_stop_keepalive EXIT INT TERM
if [[ "${SKIP_ALLSETUPS}" != true ]]; then if [[ "${SKIP_ALLSETUPS}" != true ]]; then
source ${SUBCMD_DIR}/2.setups.sh source ${SUBCMD_DIR}/2.setups.sh
fi fi