Caveat
I will present a solution here that predominantly uses C++ code. To make it more reproducible so that it can be run from within an R console I have done this using Rcpp::cppFunction. However, this is clearly not the method you would use while building a package. The resultant functions work by accessing raw pointers to R graphics devices that the user must specify, and if you call them using a non-existent device number your R session will crash.
Solution
The three copies of the GPar structure that these comments are describing are kept together in another structure called baseSystemState, which is defined here.
Each graphics device has a pointer to a baseSystemState, and we can access the graphics device using C or C++ code if we include the header file include/R_ext/GraphicsEngine.h in our own code.
However, there's a snag. Although we can get a pointer to the baseSystemState struct, our code has no idea what this actually is, since the definition of baseSystemState and GPar are not part of the public API.
So in order to read the baseSystemState and the GPars it contains, we have to redefine these structures in our own code (as Dirk suggested in his comment). Some of the members of GPar are also of types or enums that need to be defined first.
We can take these definitions, compactify them into a single string and use them as includes in a Rcpp::cppFunction call. Here is a wrapper function that does that, and therefore allows you to write C++ functions that have access to the existing graphic devices' parameters:
cppFunction_graphics <- function(s)
{
include <- paste0("#include \"", R.home("include/R_ext/GraphicsEngine.h\""))
Rcpp::cppFunction(s, includes = c(include,
"typedef enum {DEVICE= 0, NDC= 1, INCHES = 13,
NIC = 6, OMA1= 2, OMA2= 3, OMA3 = 4,OMA4= 5,NFC = 7, NPC= 16,USER= 12, MAR1 = 8,
MAR2= 9, MAR3= 10,MAR4= 11, LINES = 14, CHARS =15 } GUnit; typedef struct {
double ax; double bx; double ay; double by;} GTrans; typedef struct {int state;
Rboolean valid; double adj; Rboolean ann; rcolor bg; char bty; double cex;
double lheight; rcolor col; double crt; double din[2]; int err; rcolor fg;
char family[201]; int font; double gamma; int lab[3]; int las; int lty;
double lwd; R_GE_lineend lend; R_GE_linejoin ljoin; double lmitre; double mgp[3];
double mkh; int pch; double ps; int smo; double srt; double tck; double tcl;
double xaxp[3]; char xaxs; char xaxt; Rboolean xlog; int xpd; int oldxpd;
double yaxp[3]; char yaxs; char yaxt; Rboolean ylog; double cexbase;
double cexmain; double cexlab; double cexsub; double cexaxis; int fontmain;
int fontlab; int fontsub; int fontaxis; rcolor colmain; rcolor collab;
rcolor colsub; rcolor colaxis; Rboolean layout; int numrows; int numcols;
int currentFigure; int lastFigure; double heights[200]; double widths[200];
int cmHeights[200]; int cmWidths[200]; unsigned short order[10007]; int rspct;
unsigned char respect[10007]; int mfind; double fig[4]; double fin[2];
GUnit fUnits; double plt[4]; double pin[2]; GUnit pUnits; Rboolean defaultFigure;
Rboolean defaultPlot; double mar[4]; double mai[4]; GUnit mUnits; double mex;
double oma[4]; double omi[4]; double omd[4]; GUnit oUnits; char pty;
double usr[4]; double logusr[4]; Rboolean new_one; int devmode;
double xNDCPerChar; double yNDCPerChar; double xNDCPerLine; double yNDCPerLine;
double xNDCPerInch; double yNDCPerInch; GTrans fig2dev; GTrans inner2dev;
GTrans ndc2dev; GTrans win2fig; double scale;} GPar; typedef struct {GPar dp;
GPar gp; GPar dpSaved; Rboolean baseDevice;} baseSystemState;"),
env = parent.frame(2))
}
So now we can write a function that will extract or write to the graphics parameters of our choice from the device's starting parameters. Here, we will get our function to return a list of various colour parameters, but you can return whatever parameters you like from GPar, most of which are self-explanatory in the GPar struct definition
cppFunction_graphics("
Rcpp::List get_default_GPar(int devnum)
{
pGEDevDesc dd = GEgetDevice(devnum);
baseSystemState *bss = (baseSystemState*) dd->gesd[0]->systemSpecific;
GPar GP = bss->dp;
auto get_colour = [](rcolor rcol){
return Rcpp::NumericVector::create(
Rcpp::Named(\"red\") = rcol & 0xff,
Rcpp::Named(\"green\") = (rcol >> 8) & 0xff,
Rcpp::Named(\"blue\") = (rcol >> 16) & 0xff);
};
return Rcpp::List::create(Rcpp::Named(\"fg\") = get_colour(GP.fg),
Rcpp::Named(\"bg\") = get_colour(GP.bg),
Rcpp::Named(\"col\") = get_colour(GP.col),
Rcpp::Named(\"colmain\") = get_colour(GP.colmain),
Rcpp::Named(\"collab\") = get_colour(GP.collab),
Rcpp::Named(\"colaxis\") = get_colour(GP.colaxis));
}
")
So now in R I can ensure I have a device operating by doing:
plot(1:10)

And to access the current device's default graphics parameters I can do:
get_default_GPar(dev.cur() - 1)
#> $fg
#> red green blue
#> 0 0 0
#>
#> $bg
#> red green blue
#> 255 255 255
#>
#> $col
#> red green blue
#> 0 0 0
#>
#> $colmain
#> red green blue
#> 0 0 0
#>
#> $collab
#> red green blue
#> 0 0 0
#>
#> $colaxis
#> red green blue
#> 0 0 0
Which gives me the correct values for the default device parameters.
Now I can also write to the default device parameters if I define another function. Suppose I want to be able to change the default colour of the device's labels:
cppFunction_graphics("
void set_col(int dn, int red, int green, int blue, int alpha)
{
int new_col = red | (green << 8) | (blue << 16) | (alpha << 24);
pGEDevDesc dd = GEgetDevice(dn);
baseSystemState *bss = (baseSystemState*) dd->gesd[0]->systemSpecific;
bss->dp.collab = new_col;
}
")
Now I have a function in R that can overwrite the default label colours of the device. Let's make the default labels red:
set_col(dev.cur() - 1, 255, 0, 0, 255)
So now when I make a new plot on the same device, the labels will automatically be red:
plot(1:10)

So, as desired, you can change the device's gpars without interfering directly with par.
As for accessing the saved GPars and current GPars, this is just a case of changing the line GPar GP = bss->dp; to GPar GP = bss->gp or GPar GP = bss->dpSaved