8

I am trying to have specific settings when I start a new emacs frame in X as opposed to in the terminal while using emacs-server. My initial attempt was using this solution, but it appears it isn't working for me (using Emacs 24.4.1).

Basically, my minimimal non-working example is this:

(defun new-frame-setup (&optional frame)
  (if (display-graphic-p)
      (message "window system")
    (message "not a window system")
    ))

;; run when regular emacs is started
(new-frame-setup)
;; run when a new frame is created using server
(add-hook 'after-make-frame-functions 'new-frame-setup)

When I start regular emacs and go to my Messages buffer it says:

window system

So this works just as I expected. However, if I run the command emacsclient -c, the Messages buffer says

not a window system
Starting Emacs daemon
not a window system

The first "not a window system" makes sense, as it's starting the emacs server in the terminal. However, the second one doesn't make sense, as that frame is already a graphical window. Furthermore, after the fact, if I evaluate (display-graphic-p) in my scratch buffer, it evaluates as t. Any ideas as to what I'm doing wrong?

EDIT

So the big problem here is not that (display-graphic-p) doesn't work, it's that it doesn't know what frame to check. This was a problem for me since the daemon didn't have a window-system, but it's an even bigger problem if I have a terminal version and an X version of emacsclient running at the same time. For example, if I create a frame with emacsclient -c and have it change some settings, and then I create a frame with emacsclient -nw and have it change some other settings - all of those settings are getting changed at the global level.

So I guess the real question is: how can I get emacs to check the display of the most recently created frame, and then run some elisp code only on that frame? I have absolutely no idea if this is possible.

rottweiler
  • 83
  • 5
  • Try `focus-in-hook` instead. – Kaushal Modi Jul 14 '16 at 19:54
  • That somewhat works, but I really only want these setup functions to run once. For example, when i start an emacs client, i have it move to my left monitor - if i use `focus-in-hook`, it will make it unable to move my window after the fact. – rottweiler Jul 14 '16 at 20:09
  • Did you try using the variable `window-system` instead? – theldoria Jul 14 '16 at 20:11
  • using `window-system` gives the exact same problem. I used `display-graphic-p` because according to the docs "Use of this variable as a boolean is deprecated" – rottweiler Jul 14 '16 at 20:18
  • 1
    @rottweiler You can then remove that function from that hook from within that function. It sounds crazy, but [works](https://github.com/kaushalmodi/.emacs.d/blob/e3b32e2a7be4cf6cddc5fb3926cc4fb55a75c12d/init.el#L326-L339). :) – Kaushal Modi Jul 14 '16 at 20:28
  • @KaushalModi wow... now that is a unique solution and is pretty close! It does have two shortcomings for me though: it works great if i run `emacsclient -c myfile`, but without running it with the file argument, it doesn't immediately focus on the new client (so it moves after i click it). Second, this doesn't let me run separate code when i run it in the terminal. The `display-graphic-p` lets me use an if-else to run separate code for the different instances. – rottweiler Jul 14 '16 at 21:07
  • Actually, I can fix the first problem by calling `(raise-frame)` in the function. I think the second problem might not be a problem either, since all the "terminal" code will get called whenever i start emacs-server. – rottweiler Jul 14 '16 at 21:15
  • @rottweiler Right (to your 2nd last comment), the `focus-in-hook` is to be used in place of `after-make-frame-functions`. You still need to use `display-graphic-p` to know if you are running in terminal. But then `focus-in-hook` is not run when you run emacs[client] with -nw. So to cover that case, you need to add your fn to `after-init-hook`. You might also use `(daemonp)` to do something like [this](https://github.com/kaushalmodi/.emacs.d/blob/ff750a852a4f3501c030b95f322f418dccadcd39/setup-files/setup-linum.el#L166-L178). *<- In this example, `after-make-frame-functions` does what I need.* – Kaushal Modi Jul 14 '16 at 21:17
  • Sorry if I'm not understanding, but does that means I won't be able to run code specifically for emacs[client] with -nw? i.e. i would need to put something in an `after-init-hook` so that it gets run every time, and then selectively disable things in my `focus-in-hook`? – rottweiler Jul 15 '16 at 15:35
  • Ahhh, so I see the problem: see my edit above – rottweiler Jul 15 '16 at 16:27

1 Answers1

6

The hook on after-make-frame-functions runs after the frame is created but before it's selected (if it ever becomes selected). So you're asking whether the currently selected frame is on a graphical display, rather than whether the newly created frame is on a graphical display. This is easy to fix: pass the frame you're interested in to display-graphic-p.

(defun new-frame-setup (frame)
  (if (display-graphic-p frame)
      (message "window system")
    (message "not a window system")))

;; Run for already-existing frames
(mapc 'new-frame-setup (frame-list))
;; Run when a new frame is created
(add-hook 'after-make-frame-functions 'new-frame-setup)
  • This correctly answers my question. Even better (for my case) is to add `&optional` before `frame`, that way i can also have `(unless (daemonp) (add-hook 'after-init-hook 'new-frame-setup))` for when i run `emacs` or `emacs -nw`. I do have a follow up question though: I believe any code that gets run in `new-frame-setup` is run globally, therefore I don't believe the `mapc` line does much. Is there a way to make something run *only* on a single frame and not globally? (This may be worth a posting a new question) – rottweiler Jul 16 '16 at 18:39
  • @rottweiler I'm not sure I understand your follow-up question. You can run `(new-frame-setup some-frame)`. The reason I used `mapc` was to make the code more robust: it runs on every existing frame. You don't need any special handling for a daemon or non-daemon startup, and you don't need to use `after-init-hook`. – Gilles 'SO- stop being evil' Jul 16 '16 at 20:02
  • The main reason I want to do this is so that I can set specific settings (keybindings, mouse settings, line numbering, etc.) for emacsclient -c and emacsclient -nw, much like I currently do when I have separate instances of emacs/emacs-nw running. But for emacsserver, the main functionality of this fix is to change specific frame characteristics (like positioning and size) which are a small subset of functions. Here, if start a terminal version of emacsclient and then open a X version of emacsclient, all those new keybindings/etc get applied *globally* to all open frames. – rottweiler Jul 16 '16 at 21:49
  • I found it useful to read [`add-to-list` vs `add-hook`](https://emacs.stackexchange.com/questions/30236/add-to-list-vs-add-hook) and the [Emacs description of hooks](https://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html), which explains why we use `add-hook` here instead of `add-to-list`. We know `after-make-frame-functions` is a "hook" (an "abnormal" hook) because its name ends with "-functions". And we use `add-hook` on hooks both to make code clearly self-descriptive of what we mean, and because `add-hook` knows how to handle edge cases like the hook variable being nil. – mtraceur Mar 30 '23 at 23:00