-
-
Notifications
You must be signed in to change notification settings - Fork 972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add pulldown mode like tilda, quake and other terminals. #45
Comments
This is not possible to do in a cross-platform manner, as it depends on the details of the desktop environment/window manager. Really this function should be implemented in the window manager, so it can be performed with any window. For instance, I use a tiling window manager, and I have a key binding to instantly bring up a terminal. |
also, tdrop works well |
Does anyone have a method for OSX? Edit: I have solved this using BetterTouchTool |
@dbousamra How did you do this with BTT? |
@dbousamra Could you do this on https:/jwilm/alacritty ??? |
@zx1986 I recommend |
There's also https:/koekeishiya/skhd I build kitty in
|
I tried many ways of doing this and eventually settled on Hammerspoon. I prefer it because it's free, flexible, easy to set up programmatically in my dotfiles, and fast. (That last point is important. For example, you can avoid installing external software and instead make a shortcut in System Preferences for an Automator "Service", but there's a second-long delay after pressing the keys.) Putting this in ~/.hammerspoon/init.lua will show/hide kitty when you press CtrlSpace: hs.hotkey.bind({"ctrl"}, "space", function()
local app = hs.application.get("kitty")
if app then
if not app:mainWindow() then
app:selectMenuItem({"kitty", "New OS window"})
elseif app:isFrontmost() then
app:hide()
else
app:activate()
end
else
hs.application.launchOrFocus("kitty")
end
end) Or, if you want it to always open a new window: hs.hotkey.bind({"ctrl"}, "space", function()
local app = hs.application.get("kitty")
if app then
app:selectMenuItem({"kitty", "New OS window"})
else
hs.application.launchOrFocus("kitty")
end
end) |
@mk12 Very nice this inspired me to make this which turns Kitty into a drop down quake and iTerm like style, i bound it to hs.hotkey.bind({}, "F15", function()
local app = hs.application.get("kitty")
if app then
if not app:mainWindow() then
app:selectMenuItem({"kitty", "New OS window"})
elseif app:isFrontmost() then
app:hide()
else
app:activate()
end
else
hs.application.launchOrFocus("kitty")
app = hs.application.get("kitty")
end
app:mainWindow():moveToUnit'[100,50,0,0]'
app:mainWindow().setShadows(false)
end) |
And specifically for i3: https:/LandingEllipse/kitti3 |
I suggest adding it to |
Not a dropdown, but a very simple solution to run Kitty instead of default terminal via standard Ctrl+Alt+T shortcut:
This also allows to avoid manual desktop integration steps: |
@webberwang @NightMachinary Is there a way this will open over fullscreen apps in MacOS? |
I created a phoenix script which does this. But only in mac!
copy the following file to https:/lukesmurray/bootstrap/blob/master/.phoenix.js Use cmd+` (backtick) to get the dropdown. These are the relevant lines. You can make your own shortcut https:/lukesmurray/bootstrap/blob/a4610f273dac7ca9fba3881a4fe74a9e4c20b699/.phoenix.js#L50-L57 |
For those running yabai and skhd on macOS, this will give you a pretty good approximation of a drop down terminal. It binds CMD + `. It could probably be optimized and some yabai rules and signals could implement a little more logic and functionality, but it works well enough for me. Just drop this in your skhdrc and adjust to your liking. # SKHD
default < cmd - 0x32 : /path/to/helper
# Helper File
# NOTE: Add a port if you control of that instance.
function kitty_quake() {
HOMEBREW=/opt/homebrew/bin
QUAKE=($($HOMEBREW/yabai -m query --windows --space |jq '.[]|select(.title=="Quake")|.id,.minimized,.border'))
if [[ ${#QUAKE} -eq 0 ]]; then
open -a kitty.app -n --args -1 --title="Quake" -o \
tab_title_template="Quake: {index}" --listen-on="tcp:localhost:$PORT" \
&>/dev/null & disown;
while [[ ${#QUAKE[@]} -ne 3 ]];
do
QUAKE=($($HOMEBREW/yabai -m query --windows --space|jq '.[]|select(.title=="Quake")|.id,.minimized,.border'))
done
$HOMEBREW/yabai -m window "${QUAKE[0]}" --toggle sticky;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --move abs:0:0;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --resize abs:10000:450;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --opacity 0.95;
elif [[ ${QUAKE[1]} -eq 1 ]]; then
$HOMEBREW/yabai -m window "${QUAKE[0]}" --deminimize;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --move abs:0:0;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --resize abs:10000:450;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --opacity 0.95;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --focus "${QUAKE[0]}";
[[ ${QUAKE[2]} -eq 0 ]] && $HOMEBREW/yabai -m window "${QUAKE[0]}" --toggle border || :
elif [[ ${QUAKE[1]} -eq 0 ]]; then
[[ ${QUAKE[2]} -eq 1 ]] && $HOMEBREW/yabai -m window "${QUAKE[0]}" --toggle border || :
$HOMEBREW/yabai -m window "${QUAKE[0]}" --opacity 0.001;
$HOMEBREW/yabai -m window "${QUAKE[0]}" --minimize;
else
echo quake failed
fi
}
|
@typkrft This works really well, but I was playing with it and don't understand well enough, is there a way to modify this to run a command on startup? |
This is kind of a constantly tweaked script for me. This needs to be cleaned up and improved a lot. The current iteration I'm using is below. I've added some comments to try and explain what's going on. EDIT: function kitty_quake() {
KITTY=$(pgrep -a -f ".*kitty.*")
QUAKE=$(osascript -l JavaScript -e 'Application("System Events").processes.whose({name: "kitty"}).windows.whose({name: "Quake"}).name()')
if [[ $KITTY == "" ]]; then
open -a kitty -n --args -1 --listen-on=unix:/tmp/kitty -o macos_quit_when_last_window_closed=no false
while [[ $KITTY == "" ]]
do
KITTY=$(pgrep -a -f ".*kitty.*")
done
open -a kitty -n --args -1 --listen-on=unix:/tmp/kitty --title=Quake
while [[ $QUAKE != "Quake" ]]
do
QUAKE=$(osascript -l JavaScript -e 'Application("System Events").processes.whose({name: "kitty"}).windows.whose({name: "Quake"}).name()')
done
WINDOW_ID=$(GetWindowID kitty 'Quake')
yabai -m window $WINDOW_ID --toggle sticky
yabai -m window $WINDOW_ID --move abs:0:0
yabai -m window $WINDOW_ID --resize abs:10000:450
yabai -m window $WINDOW_ID --opacity 0.95
exit 0
elif [[ $KITTY != "" && $QUAKE == "" ]]; then
open -a kitty -n --args -1 --listen-on=unix:/tmp/kitty --title=Quake
while [[ $QUAKE != "Quake" ]]
do
QUAKE=$(osascript -l JavaScript -e 'Application("System Events").processes.whose({name: "kitty"}).windows.whose({name: "Quake"}).name()')
done
WINDOW_ID=$(GetWindowID kitty 'Quake')
yabai -m window $WINDOW_ID --toggle sticky
yabai -m window $WINDOW_ID --move abs:0:0
yabai -m window $WINDOW_ID --resize abs:10000:450
yabai -m window $WINDOW_ID --opacity 0.95
elif [[ $KITTY != "" && $QUAKE != "" ]]; then
WINDOW_ID=$(GetWindowID kitty 'Quake')
INFO=$(yabai -m query --windows --window $WINDOW_ID | jq '.border, .minimized')
BORDER=$(echo $INFO | awk '{print $1}')
MINI=$(echo $INFO | awk '{print $2}')
if [[ $MINI -eq 1 ]]; then
[[ $BORDER -eq 1 ]] && yabai -m window "$WNDOW_ID" --toggle border || :
yabai -m window "$WINDOW_ID" --deminimize;
yabai -m window "$WINDOW_ID" --opacity 0.95;
[[ $BORDER -eq 0 ]] && yabai -m window "$WNDOW_ID" --toggle border || :
yabai -m window "$WINDOW_ID" --focus "$WINDOW_ID";
elif [[ $MINI -eq 0 ]]; then
[[ $BORDER -eq 1 ]] && yabai -m window "$WINDOW_ID" --toggle border || :
yabai -m window "$WINDOW_ID" --opacity 0.001;
yabai -m window "$WINDOW_ID" --minimize;
fi
fi
} There's still a lot that needs to be tweaked, could be simplified, or written more effectively. A few things that would be very helpful would be Also querying with yabai can take a long time, proabaly due to this which causes opening and closing to take a while sometimes. This was somewhat addressed in the last edit. |
If you want to start kitty without a window run it as kitty -o macos_quit_when_last_window_closed=no false |
This does not open over fullscreen apps, but it does switch back to the fullscreen app when you hide Kitty. |
@mk12 @exoticus thank you for this suggestion! I use the following setup to get Kitty working Quake style with opacity effects on full screens in macOS:
-- Kitty configuration
hs.hotkey.bind({"cmd"}, "g", function()
-- Get current space
local currentSpace = hs.spaces.focusedSpace()
-- Get kitty app
local app = hs.application.get("kitty")
-- If app already open:
if app then
-- If no main window, then open a new window
if not app:mainWindow() then
app:selectMenuItem("New OS Window", true)
-- If app is already in front, then hide it
elseif app:isFrontmost() then
app:hide()
-- If there is a main window somewhere, bring it to current space and to front
else
-- First move the main window to the current space
hs.spaces.moveWindowToSpace(app:mainWindow(), currentSpace)
-- Activate the app
app:activate()
-- Raise the main window and position correctly
app:mainWindow():raise()
app:mainWindow():moveToUnit('0.0,0.0,1.0,1.0')
end
-- If app not open, open it
else
hs.application.launchOrFocus("kitty")
app = hs.application.get("kitty")
end
-- hs.spaces.gotoSpace(currentSpace)
end) |
This is awesome. Although I experienced two bugs with it: (1) If you close Kitty using CMD+W, then this script won't work anymore. |
If you set I would therefore recommend to use the -- toggle kitty
hs.hotkey.bind({"alt"}, "space", function()
local app = hs.application.get("kitty")
if app:isFrontmost() then
app:hide()
else
hs.application.launchOrFocus(app:name())
end
end) |
wanted to share my revision of one of the scripts using skhd / yabai from above. (@typkrft )
and you call it like this
changes:
I have it open a tmuxinator session defined in the session config. |
Code below allows you to spawn your terminal from the screen your mouse is on.
|
It worked like a charm! Ty bro! |
Did you, or anyone else, find a way to make this move the terminal to the desktop you are on. So you have this "floating window" effect like iTerm2. This works, but it doesn't function like a "drop-down" terminal, if that makes sense. |
This is already a quite long thread with many ideas and solutions but I would like to propose that this functionality becomes a native feature of Kitty. I'm aware that Kitty is a cross-platform application so the feature probably needs to be developed individually for every supported platform. However I believe that only Kitty truly knows which of the opened windows is the "Quake-style" window, so it can provide the best user experience. For example I wrote my own Phoenix script based on solutions presented here but it also has its drawbacks: For instance it's not possible to find out which of the opened Kitty windows is the pulldown window so the script will place the current active window to the top half of my screen. Also the script can only hide/show all Kitty windows at once, but I only want to toggle the pulldown window. In my workflow I usually have one pulldown terminal window and sometimes additional (OS) windows which should not interfere with the pulldown functionality. This may be a limitation of Phoenix so if somebody has a better idea of implementing this for macOS please let me know 😃 |
Just an FYI, if you use the GNOME desktop environment, there's a quake-mode extension available that works pretty well (it's even more customizable than iTerm2 on Mac). |
I wanted to create the iTerm hotkey window behaviour exactly with Kitty and Hammerspoon and ended up combining the approach from @exoticus and @mk12 - #45 (comment) with this gist from @asmagill for capturing the ctrl double tap in Hammerspoon - https://gist.github.com/asmagill/c38f75fff9d9ef43d1226329fc1436e4 Here is how you can do it. Add the following module (from the gist above) to a file called local alert = require("hs.alert")
local timer = require("hs.timer")
local eventtap = require("hs.eventtap")
local events = eventtap.event.types
local module = {}
-- Save this in your Hammerspoon configuration directiorn (~/.hammerspoon/)
-- You either override timeFrame and action here or after including this file from another, e.g.
--
-- ctrlDoublePress = require("ctrlDoublePress")
-- ctrlDoublePress.timeFrame = 2
-- ctrlDoublePress.action = function()
-- do something special
-- end
-- how quickly must the two single ctrl taps occur?
module.timeFrame = 1
-- what to do when the double tap of ctrl occurs
module.action = function()
alert("You double tapped ctrl!")
end
-- Synopsis:
-- what we're looking for is 4 events within a set time period and no intervening other key events:
-- flagsChanged with only ctrl = true
-- flagsChanged with all = false
-- flagsChanged with only ctrl = true
-- flagsChanged with all = false
local timeFirstControl, firstDown, secondDown = 0, false, false
-- verify that no keyboard flags are being pressed
local noFlags = function(ev)
local result = true
for k,v in pairs(ev:getFlags()) do
if v then
result = false
break
end
end
return result
end
-- verify that *only* the ctrl key flag is being pressed
local onlyCtrl = function(ev)
local result = ev:getFlags().ctrl
for k,v in pairs(ev:getFlags()) do
if k ~= "ctrl" and v then
result = false
break
end
end
return result
end
-- the actual workhorse
module.eventWatcher = eventtap.new({events.flagsChanged, events.keyDown}, function(ev)
-- if it's been too long; previous state doesn't matter
if (timer.secondsSinceEpoch() - timeFirstControl) > module.timeFrame then
timeFirstControl, firstDown, secondDown = 0, false, false
end
if ev:getType() == events.flagsChanged then
if noFlags(ev) and firstDown and secondDown then -- ctrl up and we've seen two, so do action
if module.action then module.action() end
timeFirstControl, firstDown, secondDown = 0, false, false
elseif onlyCtrl(ev) and not firstDown then -- ctrl down and it's a first
firstDown = true
timeFirstControl = timer.secondsSinceEpoch()
elseif onlyCtrl(ev) and firstDown then -- ctrl down and it's the second
secondDown = true
elseif not noFlags(ev) then -- otherwise reset and start over
timeFirstControl, firstDown, secondDown = 0, false, false
end
else -- it was a key press, so not a lone ctrl char -- we don't care about it
timeFirstControl, firstDown, secondDown = 0, false, false
end
return false
end):start()
return module Then, add this code (based off of @exoticus @mk12) to your ctrlDoublePress = require("ctrldouble")
ctrlDoublePress.action = function()
local app = hs.application.get("kitty")
if app then
if not app:mainWindow() then
app:selectMenuItem({"kitty", "New OS window"})
elseif app:isFrontmost() then
app:hide()
else
app:activate()
end
else
hs.application.launchOrFocus("kitty")
app = hs.application.get("kitty")
end
app:mainWindow():moveToUnit'[100,50,0,0]'
end Works like a charm on MacOS. I've remapped my capslock to ctrl and this exactly recreates the Iterm hotkey window behaviour (much faster and lower resource usage thanks to kitty) |
@prajnak Does your solution work with multiple opened OS windows of Kitty? Will it only show / hide the hotkey window while other windows are still shown? |
@svenjacobs it will hide/show the last active kitty window.. But that's ok for now for me. |
With hammerspoon it is better to select applications with The
And while in most cases getting the application by |
I've adapted the script slightly as it didn't work for me when I'm not focused on kitty and in a different Space. My use case is that I want Kitty opened permanently in one space but still be able to bring up new kitty window instances while I'm working in other spaces. hs.hotkey.bind({"ctrl"}, "space", function()
local app = hs.application.get("kitty")
local currentSpace = hs.spaces.focusedSpace()
if app then
-- There are times where main window is found and other times not
-- This check ensures that we have a main window ID set in cases where
-- it is found or set to -1 in cases where it is not found
-- this ensures that the new window call can be executed later.
if not app:mainWindow() then
mainWindowSpace = -1
else
mainWindowSpace = hs.spaces.windowSpaces( app:mainWindow():id())[1]
end
if mainWindowSpace ~= currentSpace then
app:selectMenuItem({"Shell", "New OS Window"})
hs.timer.doAfter(1, function()
app:focusedWindow():moveToUnit'[100,50,0,0]'
end)
elseif app:isFrontmost() then
app:hide()
else
app:activate()
end
else
hs.application.launchOrFocus("kitty")
app = hs.application.get("kitty")
end
end) It has something to do with how app:mainWindow is defined. The main window kept referring back to one already opened. |
I also wrote a Hammerspoon script for this: https://gist.github.com/truebit/d79b8018666d65e95970f208d8f5d149;
|
@truebit hi! getMainWindow(app):move(hs.geometry({x=0,y=0,w=0.6,h=0.4})) The width of the Kitty window is always the width of the monitor |
@I-Want-ToBelieve I tried on 13.5.1 (22G90) and 14.1 (23B74), there's no issue. maybe you could use default stage manager settings or tell me the reproducible steps |
@truebit Now I use yabai and no longer enable stage manager. I am more familiar with JavaScript than Lua, so I switched to the above solution of phoenix and yabai. Sorry to bother you. By the way, I was using mac 13.6 before |
Easiest way I found on Mac OS without third party apps. Add new Quick Action in AutomatorOpen automator -> New Quick Action. Drag the "Run Shell Script" from the left side to the right side and type: nohup /Applications/kitty.app/Contents/MacOS/kitty -1 1> /dev/null 2> /dev/null & Save it with a name like Add your shortcut from the System Settings Keyboard ShortcutsGo in System Settings -> Keyboard Shortcuts -> Services tab -> General -> Voilà. |
Another native way for
Now you have an executable icon. You can drag it into "Applications" folder if you would like. You can also now Spotlight search "Kitty" and run it as with any other app. Bonus step: Add the kitty icon to the executable by doing the following:
|
confirming the hammerspoon method works nicely on MacOS with a light footprint |
If anyone wants this on macOS with applescript I got decent solution that I'm using with AeroSpace but should be program agonostic if you use set kittyAppName to "kitty"
tell application "System Events"
if exists (window 1 of process kittyAppName) then
tell process kittyAppName
if value of attribute "AXMinimized" of window 1 is true then
set value of attribute "AXMinimized" of window 1 to false
set frontmost to true
else
set value of attribute "AXMinimized" of window 1 to true
end if
end tell
else
tell application "kitty" to activate
end if
end tell |
No description provided.
The text was updated successfully, but these errors were encountered: