Compare commits
5 Commits
50c72de4f9
...
b4653b20a2
Author | SHA1 | Date |
---|---|---|
Colin | b4653b20a2 | |
Colin | 2c011df252 | |
Colin | 9d472bb290 | |
Colin | 95b21cbed9 | |
Colin | 82007c9b40 |
|
@ -99,18 +99,12 @@ in
|
|||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
};
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText "bonsai_tree.json" (builtins.toJSON cfg.config.transitions);
|
||||
description = ''
|
||||
configuration file to pass to bonsai.
|
||||
usually auto-generated from the sibling options; exposed mainly for debugging or convenience.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fs.".config/bonsai/bonsai_tree.json".symlink.text = builtins.toJSON cfg.config.transitions;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.extraRuntimePaths = [
|
||||
"/" #< just needs "bonsai", but needs to create it first...
|
||||
|
@ -120,7 +114,7 @@ in
|
|||
description = "bonsai: programmable input dispatcher";
|
||||
partOf = [ "graphical-session" ];
|
||||
# nice -n -11 chosen arbitrarily. i hope this will allow for faster response to inputs, but without audio underruns (pipewire is -21, dino -15-ish)
|
||||
command = "nice -n -11 bonsaid -t ${cfg.config.configFile}";
|
||||
command = "nice -n -11 bonsaid -t $HOME/.config/bonsai/bonsai_tree.json";
|
||||
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -37,6 +37,18 @@ let
|
|||
hash = "sha256-gz3N4uo7IWzzqaPHHVhby/e9NbtzcFJRQwgdNYxO/Yw=";
|
||||
})
|
||||
];
|
||||
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.copyDesktopItems
|
||||
];
|
||||
desktopItems = (upstream.desktopItems or []) ++ [
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "rofi-filebrowser";
|
||||
# alternatively: `rofi -modes filebrowser -show`, however this would require theme tweaking to look good
|
||||
exec = "rofi -combi-modes filebrowser -show";
|
||||
desktopName = "rofi filebrowser";
|
||||
})
|
||||
];
|
||||
});
|
||||
# rofi-emoji = pkgs.rofi-emoji.override {
|
||||
# # plugins must be compiled against the same rofi they're loaded by
|
||||
|
|
|
@ -94,12 +94,17 @@ in
|
|||
};
|
||||
suggestedPrograms = [
|
||||
"bonsai"
|
||||
# dependencies which get pulled in unconditionally:
|
||||
"killall"
|
||||
"playerctl"
|
||||
"procps"
|
||||
"sane-open-desktop"
|
||||
"sway"
|
||||
"wireplumber"
|
||||
# optional integrations:
|
||||
"megapixels"
|
||||
"rofi"
|
||||
"xdg-terminal-exec"
|
||||
"wvkbd"
|
||||
];
|
||||
sandbox.method = "bwrap";
|
||||
|
@ -138,32 +143,27 @@ in
|
|||
sane.programs.bonsai.config.transitions = lib.mkIf cfg.enabled (friendlyToBonsai {
|
||||
# map sequences of "events" to an argument to pass to sane-input-handler
|
||||
|
||||
# map: power (short), power (short) x2, power (long)
|
||||
power_pressed.timeout.ms = 900; # press w/o release. this is a long timeout because it's tied to the "kill window" action.
|
||||
power_pressed.timeout.trigger = "powerhold";
|
||||
power_pressed.power_released.timeout.trigger = "powerbutton_one";
|
||||
power_pressed.power_released.timeout.ms = 300;
|
||||
power_pressed.power_released.power_pressed.trigger = "powerbutton_two";
|
||||
# map power (short) -> volup/voldown
|
||||
power_pressed.power_released.volup_pressed.trigger = "powerbutton_volup";
|
||||
power_pressed.power_released.voldown_pressed.trigger = "powerbutton_voldown";
|
||||
# map: power (tap), power (tap) x2
|
||||
power_pressed.power_released.trigger = "power_tap_1";
|
||||
power_pressed.power_released.timeout.ms = 600; # max time within which a second power press will be recognized
|
||||
power_pressed.power_released.power_pressed.power_released.trigger = "power_tap_2";
|
||||
# map power (hold), power tap -> hold:
|
||||
power_pressed.timeout.trigger = "power_hold";
|
||||
power_pressed.timeout.ms = 600;
|
||||
power_pressed.power_released.power_pressed.timeout.trigger = "power_tap_1_hold";
|
||||
power_pressed.power_released.power_pressed.timeout.ms = 750; # this is a long timeout because it's tied to the "kill window" action.
|
||||
|
||||
# map: power (tap) -> volup/voldown
|
||||
power_pressed.power_released.volup_pressed.trigger = "power_then_volup";
|
||||
power_pressed.power_released.voldown_pressed.trigger = "power_then_voldown";
|
||||
|
||||
# map: power + volup/voldown
|
||||
power_pressed.volup_pressed.trigger = "power_and_volup";
|
||||
power_pressed.voldown_pressed.trigger = "power_and_voldown";
|
||||
|
||||
# map: volume taps and holds
|
||||
volup_pressed = (recurseHold "volup" {}) // {
|
||||
# this either becomes volup_hold_* (via recurseHold, above) or:
|
||||
# - a short volup_tap_1 followed by:
|
||||
# - a *finalized* volup_1 (i.e. end of action)
|
||||
# - more taps/holds, in which case we prefix it with `modal_<action>`
|
||||
# to denote that we very explicitly entered this state.
|
||||
#
|
||||
# it's clunky: i do it this way so that voldown can map to keyboard/terminal in unlock mode
|
||||
# but trigger media controls in screenoff
|
||||
# in a way which *still* allows media controls if explicitly entered into via a tap on volup first
|
||||
volup_released = (volumeActions { prefix = "modal_"; }) // {
|
||||
trigger = "volup_tap_1";
|
||||
timeout.ms = 300;
|
||||
timeout.trigger = "volup_1";
|
||||
};
|
||||
volup_pressed = (volumeActions {}).volup_pressed // {
|
||||
trigger = "volup_start";
|
||||
};
|
||||
voldown_pressed = (volumeActions {}).voldown_pressed // {
|
||||
trigger = "voldown_start";
|
||||
|
|
|
@ -12,27 +12,46 @@
|
|||
#
|
||||
# example of a design which considers these things:
|
||||
# - when unlocked:
|
||||
# - volup toggle -> app menu
|
||||
# - voldown press -> keyboard
|
||||
# - voldown hold -> terminal
|
||||
# - power x2 -> screenoff
|
||||
# - hold power -> kill app
|
||||
# - volup tap -> app menu
|
||||
# - volup hold -> file browser
|
||||
# - voldown press -> keyboard
|
||||
# - voldown hold -> terminal
|
||||
# - power x2 -> screenoff
|
||||
# - power tap->hold -> kill app
|
||||
# - power,volup -> screen rotate CCW
|
||||
# - power,voldown -> screen rotate CW
|
||||
# - power+volup -> screenshot
|
||||
# - power+voldown -> camera
|
||||
# - when locked:
|
||||
# - volup tap -> volume up
|
||||
# - volup hold -> media seek forward
|
||||
# - voldown tap -> volume down
|
||||
# - voldown hold -> media seek backward
|
||||
# - power x1 -> screen on
|
||||
# - power x2 -> play/pause media
|
||||
# some trickiness allows for media controls in unlocked mode:
|
||||
# - volup tap -> enter media mode
|
||||
# - i.e. in this state, vol tap/hold is mapped to volume/seek
|
||||
# - if, after entering media mode, no more taps occur, then we trigger the default app-menu action
|
||||
# - volup tap -> volume up
|
||||
# - volup hold -> media seek forward
|
||||
# - voldown tap -> volume down
|
||||
# - voldown hold -> media seek backward
|
||||
# - power tap -> screen on
|
||||
# - power hold -> play/pause media
|
||||
# limitations/downsides:
|
||||
# - power mappings means phone is artificially slow to unlock.
|
||||
# - media controls when unlocked have quirks:
|
||||
# - mashing voldown to decrease the volume will leave you with a toggled keyboard.
|
||||
# - seeking backward isn't possible except by first tapping volup.
|
||||
# - voldown hold is over eager: easy to open terminals when phone is slow.
|
||||
# - remap to voldown tap->hold ?
|
||||
#
|
||||
# EXAMPLE EVENT FIRINGS:
|
||||
# - double-tap voldown:
|
||||
# - voldown_start
|
||||
# - voldown_tap_1
|
||||
# - voldown_tap_2
|
||||
# - hold voldown:
|
||||
# - voldown_start
|
||||
# - voldown_hold_1
|
||||
# - voldown_hold_2
|
||||
# - voldown_hold_3
|
||||
# - hold power:
|
||||
# - power_hold (notice: it doesn't fire power_start)
|
||||
# - double-tap power:
|
||||
# - power_tap_1
|
||||
# - power_tap_2
|
||||
# - power tap-then-hold:
|
||||
# - power_tap_1
|
||||
# - power_tap_1_hold
|
||||
|
||||
|
||||
|
||||
# increments to use for volume adjustment (in %)
|
||||
|
@ -106,82 +125,81 @@ handleWith() {
|
|||
|
||||
dispatchDefault() {
|
||||
case "$action" in
|
||||
"powerbutton_one")
|
||||
# power once => unlock
|
||||
handleWith allOn
|
||||
;;
|
||||
"powerbutton_two")
|
||||
"power_tap_2")
|
||||
# power twice => screenoff
|
||||
handleWith allOff
|
||||
;;
|
||||
# powerbutton_three: intentional no-op because overloading the kill-window handler is risky
|
||||
"power_hold")
|
||||
# power twice => toggle media player
|
||||
handleWith playerctl play-pause
|
||||
;;
|
||||
|
||||
volup_tap*|modal_volup_tap*)
|
||||
volup_tap*)
|
||||
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%+
|
||||
;;
|
||||
voldown_tap*|modal_voldown_tap*)
|
||||
voldown_tap*)
|
||||
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%-
|
||||
;;
|
||||
|
||||
volup_hold*|modal_volup_hold*)
|
||||
handleWith playerctl position 30+
|
||||
;;
|
||||
voldown_hold*|modal_voldown_hold*)
|
||||
handleWith playerctl position 10-
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
dispatchOff() {
|
||||
case "$action" in
|
||||
"powerbutton_two")
|
||||
# power twice => toggle media player
|
||||
handleWith playerctl play-pause
|
||||
;;
|
||||
"powerhold")
|
||||
# power toggle during deep sleep often gets misread as power hold, so treat same
|
||||
"power_tap_1")
|
||||
# power once => unlock
|
||||
handleWith allOn
|
||||
;;
|
||||
volup_hold*)
|
||||
handleWith playerctl position 30+
|
||||
;;
|
||||
voldown_hold*)
|
||||
handleWith playerctl position 10-
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
dispatchOn() {
|
||||
case "$action" in
|
||||
# powerbutton_one: intentional default to no-op
|
||||
# powerbutton_two: intentional default to screenoff
|
||||
"powerhold")
|
||||
# power thrice: kill active window
|
||||
# power_tap_1: intentional default to no-op (it's important this be unmapped, because events can be misordered with power_tap_1 arriving *after* power_tap_2)
|
||||
# power_tap_2: intentional default to screenoff
|
||||
"power_tap_1_hold")
|
||||
# power tap->hold: kill active window
|
||||
# TODO: disable this if locked (with e.g. schlock, swaylock, etc)
|
||||
handleWith swaymsg kill
|
||||
;;
|
||||
"powerbutton_volup")
|
||||
"power_and_volup")
|
||||
# power (hold) -> volup: take screenshot
|
||||
handleWith sane-open-desktop sane-screenshot.desktop
|
||||
;;
|
||||
"power_and_voldown")
|
||||
# power (hold) -> voldown: open camera
|
||||
handleWith sane-open-desktop org.postmarketos.Megapixels.desktop
|
||||
;;
|
||||
"power_then_volup")
|
||||
# power (tap) -> volup: rotate CCW
|
||||
handleWith swaymsg -- output '-' transform 90 anticlockwise
|
||||
;;
|
||||
"powerbutton_voldown")
|
||||
"power_then_voldown")
|
||||
# power (tap) -> voldown: rotate CW
|
||||
handleWith swaymsg -- output '-' transform 90 clockwise
|
||||
;;
|
||||
|
||||
"volup_tap_1")
|
||||
# swallow: this could be the start to a media control (multi taps / holds),
|
||||
# or it could be just a single tap -> release, handled next/below
|
||||
handleWith ignore
|
||||
;;
|
||||
"volup_1")
|
||||
# volume up once: system menu
|
||||
handleWith sane-open-desktop rofi.desktop
|
||||
;;
|
||||
"volup_hold_1")
|
||||
# volume up hold: just browse files
|
||||
handleWith sane-open-desktop rofi-filebrowser.desktop
|
||||
;;
|
||||
|
||||
"voldown_start")
|
||||
# volume down once: toggle keyboard
|
||||
handleWith toggleKeyboard
|
||||
;;
|
||||
"voldown_hold_2")
|
||||
"voldown_hold_1")
|
||||
# hold voldown to launch terminal
|
||||
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
|
||||
# voldown_hold_1 frequently triggers during short taps meant only to reveal the keyboard,
|
||||
# so prefer a longer hold duration
|
||||
handleWith sane-open-desktop xdg-terminal-exec.desktop
|
||||
;;
|
||||
"voldown_tap_1")
|
||||
|
@ -197,8 +215,8 @@ dispatchOn() {
|
|||
|
||||
dispatchInhibited() {
|
||||
case "$action" in
|
||||
"powerhold")
|
||||
# power thrice: escape hatch in case rofi has hung
|
||||
"power_hold")
|
||||
# power hold: escape hatch in case rofi has hung
|
||||
handleWith killall -9 rofi
|
||||
;;
|
||||
*)
|
||||
|
|
Loading…
Reference in New Issue