rofi-calc icon indicating copy to clipboard operation
rofi-calc copied to clipboard

`plot()` doesn't work

Open nonchip opened this issue 4 years ago • 13 comments

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?

nonchip avatar Feb 18 '20 18:02 nonchip

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?

svenstaro avatar Feb 24 '20 06:02 svenstaro

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 avatar Feb 24 '20 14:02 nonchip

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?

nonchip avatar Mar 03 '20 08:03 nonchip

Ok that just seems very hacky to me though :p

Perhaps try talking to qalculate upstream and figure something out?

On Tue, Mar 3, 2020, 15:10 Kyra Zimmer [email protected] wrote:

found a way less hacky (relies on compile time type mangling 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-)" sed "s/%%MANGLED_PLOTVECTORS%%/$mangled_plotvectors/" > /tmp/libqalc_persist_plot.cpp <<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 g++ -fPIC -shared -lqalculate -o libqalc_persist_plot.so /tmp/libqalc_persist_plot.cpp rm /tmp/libqalc_persist_plot.cpp

essentially this overrides this libqalculate function https://github.com/Qalculate/libqalculate/blob/9eb79b170be92cc3005ae0ebfbdbe137d9598a1f/libqalculate/Calculator-plot.cc#L167 which is called here in qalc plot https://github.com/Qalculate/libqalculate/blob/64a6f5de3127d97081cf37c2992b1fd8a177a644/libqalculate/BuiltinFunctions-util.cc#L909 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.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/svenstaro/rofi-calc/issues/40?email_source=notifications&email_token=AAAANACNJ24EMXSJ66VN7RTRFS3OBA5CNFSM4KXJRESKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOENSP4LI#issuecomment-593821229, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAANAABBOKA3BPNNI3OGQLRFS3OBANCNFSM4KXJRESA .

svenstaro avatar Mar 03 '20 08:03 svenstaro

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 avatar Mar 03 '20 09:03 nonchip

damnit sorry misclicked :P

nonchip avatar Mar 03 '20 10:03 nonchip

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.

svenstaro avatar May 03 '20 00:05 svenstaro

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)

nonchip avatar May 03 '20 05:05 nonchip

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

svenstaro avatar May 03 '20 05:05 svenstaro

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

nonchip avatar May 03 '20 06:05 nonchip

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.

svenstaro avatar May 03 '20 06:05 svenstaro

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).

nonchip avatar May 03 '20 07:05 nonchip

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

svenstaro avatar Feb 07 '22 06:02 svenstaro