Skip to content
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

plot() doesn't work #40

Open
nonchip opened this issue Feb 18, 2020 · 13 comments
Open

plot() doesn't work #40

nonchip opened this issue Feb 18, 2020 · 13 comments

Comments

@nonchip
Copy link

nonchip commented Feb 18, 2020

it just seems to keep opening and instantly closing gnuplot windows in an endless loop even if i'm not typing anymore, while saying the function returns 0 in the rofi window.

seems like you're just running and instantly killing off (well technically it's killing itself after the input command ended, but maybe that could be fixed by using a temporary data file and running gnuplot after the fact?) qalc over and over in a busy loop instead of just when the input changed?

qalc "plot (x^2)" does exactly the same, but once: flashes open a gnuplot window and then takes it with it as soon as it dies. maybe we could just prevent qalc from killing its child and do that ourselves before restarting it after the next keypress?

@svenstaro
Copy link
Owner

Frankly, I didn't really expect plot to work but I see now that it just might work. Do you wanna take a stab at fixing this issue?

@nonchip
Copy link
Author

nonchip commented Feb 24, 2020

a pretty ugly hack i've discovered (though that'll mess with the stdout formatting, but with plot that can be ignored, since that function always seems to return 0 anyway) would be to:

  1. detect a call to plot
  2. run qalc -i $expression to make it drop into interactive mode after the expression is executed
  3. ptrace its execve syscall or doing something similar to get the pid of its gnuplot child and watch for it to exit
  4. send EOF to exit from interactive mode.

to make sure you instantly trace it to get the PID before it has a chance to fork itself, you can instead of just calling the process with system or similar do:

  1. manually fork
  2. let the child ptrace(PTRACE_TRACEME,...), this makes it instantly stop
  3. let the parent set up a breakpoint on its fork syscalls to catch the subprocess for gnuplot being spawned
  4. let the parent run a loop to watch said breakpoint and keep sending it SIGCONTs afterwards till it's dead.
  5. manually let the child execve("qalc",...), this replaces it with the qalc process but lets the trace attached.

as i said, very ugly hack; a better idea would probably be to make qalc itself do the waiting when it spawns a child, maybe we should bring this up with the qalculate developers? you can't tell me qalc "plot(x^2)" instantly murderizing both itself and the gnuplot after running it is sane behaviour :P

@nonchip
Copy link
Author

nonchip commented Mar 3, 2020

found a way less hacky (relies on compile time type mangling extracted from libqalculate because gcc doesn't have a __FUNCDNAME__ equivalent, but doesn't require hacking into another process) solution with LD_PRELOAD:

#!/bin/sh
mangled_plotvectors="_$(objdump -T /usr/lib/libqalculate.so | grep plotVectors | cut -d_ -f2-)"
g++ -fPIC -shared -lqalculate -o libqalc_persist_plot.so -x c++ - <<END
#include <libqalculate/Calculator.h>
#include <cxxabi.h>
#include <dlfcn.h>

typedef bool (Calculator::*_functype)(PlotParameters *, const std::vector<MathStructure> &, const std::vector<MathStructure> &, std::vector<PlotDataParameters*> &, bool, int);

bool Calculator::plotVectors(PlotParameters *param, const std::vector<MathStructure> &y_vectors, const std::vector<MathStructure> &x_vectors, std::vector<PlotDataParameters*> &pdps, bool persistent, int msecs) {
  static _functype _orig = nullptr;
  if(_orig==nullptr){
    void *tmpPtr = dlsym(RTLD_NEXT,"${mangled_plotvectors}");
    memcpy(&_orig,&tmpPtr,sizeof(void *));
  }
  return (this->*_orig)(param, y_vectors, x_vectors, pdps, true, msecs);
}
END

essentially this script looks up the mangled name (because it would be waaay too much to ask of the compiler which did the mangling anyway to expose it riiiight -_-) and generates code to override this libqalculate function which is called here in qalc plot with persistent=false to instead assume persistent=true, thus not exiting the gnuplot.

again this is something qalc should probably do itself when not run in interactive mode, but for now it's better than nothing and way neater than my kernel-debug-interface-hackery above.

if you want to integrate it, you just need to run this script on compile time to generate the libqalc_persist_plot.so and then make your code run qalc with LD_PRELOAD set to its path.

alternatively if you're not too comfortable with integrating the hack in the project itself you could tell people if they need it to just do it themselves and set LD_PRELOAD for rofi, because that just doesn't use the lib it wouldn't get in the way, and the environment is shared with the qalc child, so it should still get it.

then there still would need to be some kind of logic to prevent qalc being run in an endless loop like it is now, to prevent spawning an endless number of gnuplots, maybe just by looking for the plot call and only running it if the user presses enter (just add another button like the "add to history" for "run the plot") or something like that?

@svenstaro
Copy link
Owner

svenstaro commented Mar 3, 2020 via email

@nonchip
Copy link
Author

nonchip commented Mar 3, 2020

just did that, see mentioned issue above, apparently it's already fixed. that just means we need the "don't endlessly loop when plotting" logic, and wait for an upstream release. maybe actually prevent the user from calling plot altogether (by checking for the "plot" substring in the input and skipping the call to qalc), and just add a "plot whatever you entered" button next to the "add to history" one that wraps the input in plot(...) and does a single call?

@nonchip nonchip closed this as completed Mar 3, 2020
@nonchip nonchip reopened this Mar 3, 2020
@nonchip
Copy link
Author

nonchip commented Mar 3, 2020

damnit sorry misclicked :P

@svenstaro
Copy link
Owner

Ok so with #42 fixed and the upstream fix now generally available in 3.9.0, do you want to try to take a stab at this? Do you actually want to go ahead and define a shortcut for this perhaps? Plotting would be kinda cool.

@nonchip
Copy link
Author

nonchip commented May 3, 2020

i think the only thing missing for this would be to make sure you don't accidentally spawn a new qalc while there's already one running. the easiest way would probably be to just store the process you create in calc_preprocess_input, then at the beginning of that function check something like process==NULL || g_subprocess_get_if_exited(process) and just return doing nothing if it's still running.
oh that and checking something like strstr(input,"plot")!=NULL and in that case only actually doing the thing when the user presses enter, or, maybe preferably, just add another button like the "add to history" one that actually calls plot(whatever the input was)

@svenstaro
Copy link
Owner

Sounds like you already got a plan. Would be great if you could make a PR for this.

@nonchip
Copy link
Author

nonchip commented May 3, 2020

i'm afraid i don't really have experience with how rofi's menu system works though, i can see in https://github.com/svenstaro/rofi-calc/blob/master/src/calc.c#L445 you're doing something with the "add to history" button (because the string is mentioned there), but i have no idea how to add another button to that without screwing up all the indices for the actual history

@svenstaro
Copy link
Owner

How about instead of a new visual button you choose to use one of rofi's many custom commands (check help)? For instance, Alt-1 would work nicely I think.

@nonchip
Copy link
Author

nonchip commented May 3, 2020

sure might work, but again, no idea how to implement it, and neither the man page mentions alt-1 or custom commands, nor does the wiki or examples folder on their github seem to actually document modes written in something else than bash scripts, so i don't really know what help you want me to check there.
again, my only experience with rofi is a) using it as mostly a command runner and b) hitting ctrl+f inside your code.

oh and from a UX perspective i'm not sure if a custom key would be the way to go, with a button you can at least see that it's there, and technically i guess the "just detect the word plot" approach is probably gonna be most intuitive, but no idea how to then detect pressing enter (except by conditionally adding a button, because the focus seems to always be in the history, not the input/search box).

@svenstaro
Copy link
Owner

@nonchip Can you check out #66 and see whether that works for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants