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
|
||||
{
|
||||
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
|
||||
# by name without wiring each through nix.
|
||||
@@ -521,7 +521,25 @@ 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
|
||||
# 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;
|
||||
swallow_regex = "^(foot)$";
|
||||
};
|
||||
|
||||
+20
-4
@@ -1,7 +1,9 @@
|
||||
# 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
|
||||
# other desktop tooling. The only personal value is the right-panel start dir,
|
||||
# exposed as an option defaulting to the consumer's own home.
|
||||
# Midnight Commander: TUI file manager. Its config (ini, panels, F2 user menu,
|
||||
# mc.ext.ini file associations) is opinionated but generic, so it lives in the
|
||||
# public baseline alongside the other desktop tooling. The only personal value
|
||||
# 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
|
||||
# the extension), so it ships with mc to keep the menu self-contained.
|
||||
@@ -37,16 +39,30 @@
|
||||
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 = {
|
||||
home.packages = [ pkgs.mc pkgs.ouch ];
|
||||
|
||||
local.mc.menu = lib.mkBefore (builtins.readFile ./assets/mc/menu);
|
||||
local.mc.extIni = lib.mkBefore (builtins.readFile ./assets/mc/mc.ext.ini);
|
||||
|
||||
xdg.configFile = {
|
||||
"mc/ini".source = ./assets/mc/ini;
|
||||
"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
|
||||
# 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