Keypirinha is an open-source fast launcher which boosts your efficiency with hotkeys, keywords, text expansion and more. It allows users to quickly find and launch applications, files, bookmarks, URLs, and even perform tasks like evaluating mathematical expressions, hashing strings, and generating random UUIDs.
It features a plugin-oriented architecture, meaning its functionality is largely driven by plugins, which help populate its internal database (the Catalog) with searchable items. Users can configure Keypirinha manually via INI files, as it does not have a graphical interface for configuration.
Unfortunately, Keypirinha appears to have been discontinued, with no new updates since November 8, 2020 (v2.26) although it remains fully functional.
Last week I was wondering how I can easily search and copy values from different csv files. After some hours of searching I wasn’t able to detect any existing solution. So I set to deploy my own Search CSV package for Keypirinha.
Installing Keypirnha
1) Download latest release (v2.26) from the official page (I prefer the full version (portable))
2) Extract it to your desired location
3) Run keypirinha.exe
from the extracted folder Keypirinha
4) The application’s icon will be visible on the taskbar—if it’s not immediately seen, check the hidden icons section.
5) Clicking the app’s icon will bring up the launcher.
6) Start typing configure
, then choose the Keypirinha: Configure
option from the suggestions list.
7) The first time you select this action, two text editors will open. The left editor provides a read-only documentation reference, while the right displays your current configuration. The Official Documentation is comprehensive and well-structured, offering various customization options such as launch shortcut combinations, package management, styling and more.
Configuring Keypirnha
My preferred configuration scheme, running on Windows 11, is the following:
[app]
hotkey_run = Ctrl + Shift + S
hotkey_paste = Ctrl + Shift + A
hotkey_history = Ctrl + Shift + E
write_log_file = yes
ignored_packages = <all>,-PhoneBook
[gui]
theme = DarkSpotlight
always_on_top = yes
hide_on_focus_lost = yes
[theme/DarkSpotlight]
font_face = SF Pro Text, Helvetica, Tahoma, Arial, Segoe UI
font_small_size = 12
font_small_style = cleartype
font_snormal_size = 14
font_snormal_style = cleartype
font_normal_size = 16
font_normal_style = cleartype
font_large_size = 22
font_large_style = cleartype
mono_font_face = Fira Code, Consolas
color_background = #111
color_foreground = #fff
color_faded = #888
color_accent = #fdf16f
color_textbox_back = ${color_background}
color_listitem_back = ${color_background}
color_listitem_title = ${color_foreground}
color_listitem_desc = ${color_accent}
color_listitem_selected_back = #116dd6
color_listitem_selected_title = ${color_foreground}
color_listitem_selected_desc = ${color_accent}
listitem_padding = 10
opacity_back = 92
layout = list_icon
satellite_show = never
Specifically my configuration:
1) Further enhances the appearance by slightly increasing font size and spacing.
2) Keeps the launcher always on top of other applications and hides it when it loses focus.
3) Sets launch shortcut combination to {Ctrl}
+{Shift}
+{S}
, launch and automatically paste current clipboard to {Ctrl}
+{Shift}
+{A}
and launch showing the history of previous selected items to {Ctrl}
+{Shift}
+{E}
4) It disables all preinstalled packages, except for the one that will be deployed in the next steps to search within a specific CSV file.
Packages
Keypirinha is shipped with some packages, also called Official Packages that are enabled by default. You may find some of them really interesting and helpful for your daily tasks. You can find more for the shipped packages in the Official Documentation and you can enable any package at will, by appending it’s Name prefixed with a hypen(-) to the ignored_packages =
similar to the PhoneBook package in the configuration mentioned earlier.
Search CSV Package
Official Documentation also describes how to create a New Package. This implementation searches a CSV file for the text entered in the launcher and displays a list of possible matches. When a match is selected, the corresponding value is copied to the clipboard. Everything is fully configurable through properties, requiring no code modifications, and it’s completely portable—no installation needed.
For this demo, I will create a package named PhoneBook
(case-sensitive). You can create as many packages as needed, especially if working with multiple CSV files. Just make sure to name your classes exactly the same as your package to prevent conflicts.
Instructions
1) First navigate to the directory where you extracted Keypirinha. And create a new folder under the portable/Profile/packages/
named PhoneBook
.
2) In this folder move your CSV file which will be used for searching. (I generated one from Mockaroo.com with 1000 records)
3) In this folder create a new Python filed named main.py
and append the following code:
import keypirinha as kp
import keypirinha_util as kpu
import csv, json
import os
# EDIT PROPERTIES BASED ON YOUR OWN FILE AND REQUIREMENTS
# specify columns to search, leaving it empty will search every column
# less columns will result in faster resolve times
SEARCH_COLUMNS = ['first_name', 'last_name']
CASE_SENSITIVE = False
# suggestions will appear after typing 3 characters
MINIMUM_CHARACTERS_INPUT = 3
# maximum number of suggestions to display
MAX_RESULTS = 5
# Each result has two rows, the first one is the label and the second the description. If you define more than one columns it will join them using space
LABEL_COLUMNS = ['first_name','last_name']
DESCRIPTION_COLUMNS = ['phone_number','email', 'company']
# when you hit enter or double click this column will be copied to clipboard
TARGET_COLUMNS = ['phone_number']
# Where your CSV file is located
RELATIVE_FILEPATH = "data.csv" # set it to None and it will use the FILEPATH instead
FILEPATH = "c:\data.csv" # is not used if RELATIVE_FILEPATH is defined
# ------------------- END OF PROPERTIES -------------------
class PhoneBook(kp.Plugin):
def __init__(self):
super().__init__()
self._debug = True
self.csv_data = []
def on_start(self):
self.dbg("PhoneBook started")
if(RELATIVE_FILEPATH):
self.csv_data = self._load_csv(os.path.join(os.path.dirname(os.path.realpath(__file__)), RELATIVE_FILEPATH))
else:
self.csv_data = self._load_csv(FILEPATH)
if self.csv_data:
self.dbg("CSV file loaded successfully.")
else:
self.warn("Failed to load CSV file.")
def on_catalog(self):
self.dbg("on_catalog called")
self.set_catalog([self.create_item(
category=kp.ItemCategory.USER_BASE,
label="Search CSV",
short_desc="Search CSV file",
target="search_csv",
args_hint=kp.ItemArgsHint.REQUIRED,
hit_hint=kp.ItemHitHint.KEEPALL
)])
def on_suggest(self, user_input, items_chain):
count = 0
if len(user_input) < MINIMUM_CHARACTERS_INPUT:
return
if not CASE_SENSITIVE:
search_term = user_input.lower()
else:
search_term = user_input
if not items_chain and search_term:
suggestions = []
for row in self.csv_data:
# Early termination if we've reached our limit
if len(suggestions) >= MAX_RESULTS:
break
for col in self.search_columns:
cell_value = row[col] if CASE_SENSITIVE else row[col].lower()
if search_term in cell_value:
label = ' '.join(str(row[col]) for col in LABEL_COLUMNS if col in row)
description = ' '.join(str(row[col]) for col in DESCRIPTION_COLUMNS if col in row)
suggestions.append(self.create_item(
category=kp.ItemCategory.USER_BASE,
label=label,
short_desc=description,
target=json.dumps(row),
args_hint=kp.ItemArgsHint.FORBIDDEN,
hit_hint=kp.ItemHitHint.KEEPALL
))
break
self.set_suggestions(suggestions, kp.Match.ANY, kp.Sort.NONE)
def on_execute(self, item, action):
self.dbg(f"Selected row: {item.target()}")
# Handle cases where modifier is None
row = json.loads(item.target())
clipboard = ' '.join(str(row[col]) for col in TARGET_COLUMNS if col in row)
kpu.set_clipboard(clipboard)
def _load_csv(self, path):
self.dbg(f"Loading CSV file from: {path}")
if(not os.path.exists(path)):
self.warn(f"CSV file: {path} does not exist")
raise RuntimeError("Plugin loading aborted because CSV file does not exist")
try:
with open(path, newline='', encoding='utf-8') as csvfile:
# Peek at the first line to determine if we have headers
first_line = csvfile.readline()
csvfile.seek(0) # Rewind to beginning
if first_line.strip(): # If file is not empty
# Try to determine if first line contains headers
sniffer = csv.Sniffer()
has_header = sniffer.has_header(first_line)
if has_header:
# Use DictReader if we have headers
csvreader = csv.DictReader(csvfile)
self.headers = csvreader.fieldnames
data = list(csvreader)
all_columns = list(set(SEARCH_COLUMNS + LABEL_COLUMNS + DESCRIPTION_COLUMNS + TARGET_COLUMNS))
for col in all_columns:
if not col in self.headers:
self.warn(f"CSV File does not contain column: {col}")
raise RuntimeError("Plugin loading aborted due to missing column")
else:
self.warn("CSV File has no headers! Plugin will not load")
raise RuntimeError("Plugin loading aborted due to missing headers")
# Set search columns
if len(SEARCH_COLUMNS)==0:
self.search_columns = self.headers if self.headers else list(range(len(data[0])))
else:
self.search_columns = SEARCH_COLUMNS
self.dbg(f"Will search in the following columns: {self.search_columns}")
return data
else:
self.warn("CSV File os empty")
raise RuntimeError("Plugin loading aborted because CSV file is empty")
except Exception as e:
self.warn(f"Error loading CSV file: {e}")
raise RuntimeError("Plugin loading aborted")
The code is self-explanatory and the only things you need to modify are the global constants (UPPERCASE properties).
Results
The final result is a launcher activated by pressing {Ctrl}
+{Shift}
+{S}
, allowing you to start typing a search name or last name. If you’ve already copied the name, pressing {Ctrl}
+{Shift}
+{E}
will launch the app and automatically paste the clipboard contents. Once you locate the result, double-clicking or selecting it with the arrow keys and pressing Enter
will copy the telephone number to the clipboard.
If you want you can use Search CSV to deploy separate packages for more than one CSV files. Just remember to name your package folder and your Class (in python code) with the same name to avoid conflicts.
Troubleshooting
The code is using the recommended debugging functionality, so using Keypirinha’s Console (By right clicking taskbar’s icon and selecting Open Console
) you will be able to see Debug and Error Messages.
Code
The full code is available on github