feat(mc): open files with their configured app via xdg-open
Ship `mc.ext.ini` as the `local.mc.extIni` option (like `local.mc.menu`): Enter browses archives in-place as before and delegates every other type to `xdg-open`. Add public `mime.nix` with `xdg.mimeApps` defaults (pdf -> zathura, images -> imv, audio/video -> mpv, office -> libreoffice) -- public because those handlers are; personal browser/mail defaults stay private. Also document, at the Hyprland swallow config, how swallowing differs from an app opening fullscreen/maximized (LibreOffice restores its own maximized state and looks swallowed but isn't).
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
# Midnight Commander file associations (public baseline).
|
||||||
|
#
|
||||||
|
# mc reads a SINGLE mc.ext.ini; this user copy fully replaces the system one
|
||||||
|
# (they are not merged), so it must also carry the archive entries we keep.
|
||||||
|
#
|
||||||
|
# Policy: Enter (Open) on an archive mounts it as a virtual filesystem (mc
|
||||||
|
# built-in, no external helper). Every other file falls through to [Default],
|
||||||
|
# which runs `xdg-open` -- so the app is whatever the XDG mimeapps default
|
||||||
|
# resolves to (set those in the private inventory's xdg.mimeApps). One source of
|
||||||
|
# truth, shared with every other launcher; nothing app-specific baked in here.
|
||||||
|
#
|
||||||
|
# Note: office documents (.docx/.odt/.xlsx/...) are ZIP containers, so they are
|
||||||
|
# deliberately absent from the [zip] entry below -- otherwise Enter would mount
|
||||||
|
# them as archives instead of handing them to LibreOffice via xdg-open.
|
||||||
|
#
|
||||||
|
# Override contract (this is a types.lines option shipped as mkBefore): append a
|
||||||
|
# same-named section to override one key -- mc merges same-named sections and
|
||||||
|
# the last key wins -- e.g. add a [Default] with a different Open to change the
|
||||||
|
# fallback, or add [pdf] Type=^PDF / Open=mupdf %f & to force one type to a
|
||||||
|
# specific app. lib.mkForce "..." replaces this baseline wholesale.
|
||||||
|
|
||||||
|
[mc.ext.ini]
|
||||||
|
Version=4.0
|
||||||
|
|
||||||
|
### Archives: Enter mounts them as a virtual filesystem (F3/F4 unaffected) ###
|
||||||
|
|
||||||
|
[tar]
|
||||||
|
Regex=\\.(tar(\\.(gz|bz2|xz|zst|lz|lz4|lzo|Z))?|t(gz|az|bz2?|xz|zst|lz))$
|
||||||
|
Open=%cd %p/utar://
|
||||||
|
|
||||||
|
[zip]
|
||||||
|
Regex=\\.(zip|jar|xpi|whl|egg|apk|war|ear)$
|
||||||
|
Open=%cd %p/uzip://
|
||||||
|
|
||||||
|
[7z]
|
||||||
|
Shell=.7z
|
||||||
|
Open=%cd %p/u7z://
|
||||||
|
|
||||||
|
[rar]
|
||||||
|
Regex=\\.r(ar|[0-9][0-9])$
|
||||||
|
Open=%cd %p/urar://
|
||||||
|
|
||||||
|
[arj]
|
||||||
|
Regex=\\.a(rj|[0-9][0-9])$
|
||||||
|
Open=%cd %p/uarj://
|
||||||
|
|
||||||
|
[cab]
|
||||||
|
Shell=.cab
|
||||||
|
Open=%cd %p/ucab://
|
||||||
|
|
||||||
|
[cpio]
|
||||||
|
Regex=\\.cpio(\\.(gz|xz|bz2|zst|lz|lz4|lzo|Z))?$
|
||||||
|
Open=%cd %p/ucpio://
|
||||||
|
|
||||||
|
[deb]
|
||||||
|
Regex=\\.u?deb$
|
||||||
|
Open=%cd %p/deb://
|
||||||
|
|
||||||
|
[rpm]
|
||||||
|
Regex=\\.(rpm|src\\.rpm|spm)$
|
||||||
|
Open=%cd %p/rpm://
|
||||||
|
|
||||||
|
[iso]
|
||||||
|
Shell=.iso
|
||||||
|
Open=%cd %p/iso9660://
|
||||||
|
|
||||||
|
[patch]
|
||||||
|
Regex=\\.(diff|patch)(\\.(gz|bz2|xz|zst|Z))?$
|
||||||
|
Open=%cd %p/patchfs://
|
||||||
|
|
||||||
|
### Everything else: hand off to the desktop's configured default app ###
|
||||||
|
|
||||||
|
# `&` backgrounds xdg-open so mc's shell returns and the launched GUI is orphaned
|
||||||
|
# to init. Hyprland only swallows the terminal when the new window's parent-PID
|
||||||
|
# chain still has a foot ancestor (getSwallower in Hyprland's Window.cpp), so an
|
||||||
|
# orphaned app opens as its own tile instead of swallowing mc (zathura, imv, ...).
|
||||||
|
# %f MUST stay unquoted: mc already backslash-escapes the path, so quoting turns
|
||||||
|
# those backslashes literal and breaks filenames with spaces.
|
||||||
|
|
||||||
|
[Default]
|
||||||
|
Open=xdg-open %f &
|
||||||
+20
-2
@@ -81,7 +81,7 @@ let
|
|||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ];
|
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ];
|
||||||
|
|
||||||
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
|
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
|
||||||
# by name without wiring each through nix.
|
# by name without wiring each through nix.
|
||||||
@@ -521,7 +521,25 @@ in
|
|||||||
# Matches on the *parent* terminal's class. Needs standalone foot: in
|
# Matches on the *parent* terminal's class. Needs standalone foot: in
|
||||||
# server/client mode all windows share one process, so PID ancestry can't
|
# server/client mode all windows share one process, so PID ancestry can't
|
||||||
# tell them apart and swallowing breaks — that's why $mod+Return runs
|
# tell them apart and swallowing breaks — that's why $mod+Return runs
|
||||||
# `foot`, not `footclient`.
|
# `foot`, not `footclient`. Swallow is decided by getSwallower() walking
|
||||||
|
# the new window's parent-PID chain (up to 25 hops) for a window whose
|
||||||
|
# class matches swallow_regex; swallow_exception_regex, if set, matches the
|
||||||
|
# *terminal's title*, not the new window's class (a common misreading).
|
||||||
|
#
|
||||||
|
# Do NOT confuse swallowing with an app opening fullscreen/maximized — they
|
||||||
|
# look identical (the launching terminal vanishes) but are different things:
|
||||||
|
# - real swallow removes the terminal from the layout and restores it only
|
||||||
|
# when the swallower closes; you cannot un-swallow a live window.
|
||||||
|
# - a fullscreen/maximized app merely covers the workspace; the launcher
|
||||||
|
# is still a tile underneath and $mod+f / $mod SHIFT f toggles it back.
|
||||||
|
# `hyprctl clients` is decisive: a real swallow shows the terminal
|
||||||
|
# hidden=true and the new window swallowing!=0x0. LibreOffice trips this —
|
||||||
|
# it restores its own maximized state on open (from mc or a shell), so a
|
||||||
|
# document appears to "swallow" mc when it is only fullscreen. And being
|
||||||
|
# single-instance (one soffice.bin owns every window via IPC handoff), when
|
||||||
|
# it genuinely does swallow, that is fixed by whatever launched its daemon,
|
||||||
|
# not per-open — so it can't do the tile-from-mc / swallow-from-shell split
|
||||||
|
# that single-process handlers like zathura/imv get for free.
|
||||||
enable_swallow = true;
|
enable_swallow = true;
|
||||||
swallow_regex = "^(foot)$";
|
swallow_regex = "^(foot)$";
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-4
@@ -1,7 +1,9 @@
|
|||||||
# Midnight Commander: TUI file manager. Its config (ini, panels, F2 user menu)
|
# Midnight Commander: TUI file manager. Its config (ini, panels, F2 user menu,
|
||||||
# is opinionated but generic, so it lives in the public baseline alongside the
|
# mc.ext.ini file associations) is opinionated but generic, so it lives in the
|
||||||
# other desktop tooling. The only personal value is the right-panel start dir,
|
# public baseline alongside the other desktop tooling. The only personal value
|
||||||
# exposed as an option defaulting to the consumer's own home.
|
# is the right-panel start dir, exposed as an option defaulting to the
|
||||||
|
# consumer's own home. mc.ext.ini keeps archive browsing but routes every other
|
||||||
|
# type to xdg-open, so the app choices live in xdg.mimeApps (baseline: mime.nix).
|
||||||
#
|
#
|
||||||
# ouch backs the F2 pack/unpack entries (one tool, archive format inferred from
|
# ouch backs the F2 pack/unpack entries (one tool, archive format inferred from
|
||||||
# the extension), so it ships with mc to keep the menu self-contained.
|
# the extension), so it ships with mc to keep the menu self-contained.
|
||||||
@@ -37,16 +39,30 @@
|
|||||||
lib.mkForce to replace it entirely.
|
lib.mkForce to replace it entirely.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extIni = lib.mkOption {
|
||||||
|
type = lib.types.lines;
|
||||||
|
description = ''
|
||||||
|
Contents of mc's mc.ext.ini (file-type associations driving Enter/F3/F4).
|
||||||
|
The shipped baseline keeps mc's archive browsing and delegates every
|
||||||
|
other type to xdg-open, so the actual app is the XDG mimeapps default.
|
||||||
|
Like `menu`, it is a mkBefore definition: append a same-named section to
|
||||||
|
override one key (mc merges same-named sections, last key wins), or use
|
||||||
|
lib.mkForce to replace it entirely.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
home.packages = [ pkgs.mc pkgs.ouch ];
|
home.packages = [ pkgs.mc pkgs.ouch ];
|
||||||
|
|
||||||
local.mc.menu = lib.mkBefore (builtins.readFile ./assets/mc/menu);
|
local.mc.menu = lib.mkBefore (builtins.readFile ./assets/mc/menu);
|
||||||
|
local.mc.extIni = lib.mkBefore (builtins.readFile ./assets/mc/mc.ext.ini);
|
||||||
|
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"mc/ini".source = ./assets/mc/ini;
|
"mc/ini".source = ./assets/mc/ini;
|
||||||
"mc/menu".text = config.local.mc.menu;
|
"mc/menu".text = config.local.mc.menu;
|
||||||
|
"mc/mc.ext.ini".text = config.local.mc.extIni;
|
||||||
|
|
||||||
# Generated (not a static asset) so other_dir tracks the option instead of
|
# Generated (not a static asset) so other_dir tracks the option instead of
|
||||||
# baking in a personal path.
|
# baking in a personal path.
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Default applications per file type -- the single source of truth for "open
|
||||||
|
# with" across the desktop: xdg-open, file pickers, and Enter in mc (whose
|
||||||
|
# mc.ext.ini delegates every non-archive type to xdg-open; see mc.nix).
|
||||||
|
#
|
||||||
|
# These belong in the public baseline because the handlers do too: zathura, imv
|
||||||
|
# and mpv come from media.nix, libreoffice from home.nix. So "PDF opens in
|
||||||
|
# zathura", "images in imv", etc. are the natural defaults of this baseline, not
|
||||||
|
# personal choices -- every entry is mkDefault, so a consumer overrides one type
|
||||||
|
# by setting the same key (their definition wins), or flips enable off entirely.
|
||||||
|
#
|
||||||
|
# Personal handlers -- web browser, mail -- are NOT here; they map to a specific
|
||||||
|
# browser profile / mail client and live in the private inventory, merging into
|
||||||
|
# the same defaultApplications attrset.
|
||||||
|
|
||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Map every mime in the list to one .desktop id, at mkDefault priority.
|
||||||
|
forAll = desktop: mimes: lib.genAttrs mimes (_: lib.mkDefault desktop);
|
||||||
|
|
||||||
|
zathura = "org.pwmt.zathura.desktop";
|
||||||
|
imv = "imv.desktop";
|
||||||
|
mpv = "mpv.desktop";
|
||||||
|
writer = "writer.desktop";
|
||||||
|
calc = "calc.desktop";
|
||||||
|
impress = "impress.desktop";
|
||||||
|
in {
|
||||||
|
xdg.mimeApps.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
xdg.mimeApps.defaultApplications =
|
||||||
|
(forAll zathura [
|
||||||
|
"application/pdf"
|
||||||
|
"application/epub+zip"
|
||||||
|
"application/oxps"
|
||||||
|
"application/postscript"
|
||||||
|
])
|
||||||
|
// (forAll imv [
|
||||||
|
"image/png"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/gif"
|
||||||
|
"image/webp"
|
||||||
|
"image/bmp"
|
||||||
|
"image/tiff"
|
||||||
|
])
|
||||||
|
// (forAll mpv [
|
||||||
|
"video/mp4"
|
||||||
|
"video/x-matroska"
|
||||||
|
"video/webm"
|
||||||
|
"video/x-msvideo"
|
||||||
|
"video/quicktime"
|
||||||
|
"video/mpeg"
|
||||||
|
"audio/mpeg"
|
||||||
|
"audio/flac"
|
||||||
|
"audio/ogg"
|
||||||
|
"audio/x-wav"
|
||||||
|
"audio/mp4"
|
||||||
|
"audio/x-opus+ogg"
|
||||||
|
])
|
||||||
|
// (forAll writer [
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
"application/msword"
|
||||||
|
"application/vnd.oasis.opendocument.text"
|
||||||
|
"application/rtf"
|
||||||
|
])
|
||||||
|
// (forAll calc [
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
"application/vnd.ms-excel"
|
||||||
|
"application/vnd.oasis.opendocument.spreadsheet"
|
||||||
|
])
|
||||||
|
// (forAll impress [
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||||
|
"application/vnd.ms-powerpoint"
|
||||||
|
"application/vnd.oasis.opendocument.presentation"
|
||||||
|
]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user