1 /*
2  * IceWM
3  *
4  * Copyright (C) 1997-2002 Marko Macek
5  */
6 #include "config.h"
7 
8 #include "wmprog.h"
9 #include "prefs.h"
10 #include "wmwinmenu.h"
11 #include "wmapp.h"
12 #include "themes.h"
13 #include "browse.h"
14 #include "appnames.h"
15 #include "wmpref.h"
16 #include "wmswitch.h"
17 #include "intl.h"
18 #include <sys/stat.h>
19 #include <time.h>
20 
DFile(IApp * app,const mstring & name,ref<YIcon> icon,upath path)21 DFile::DFile(IApp *app, const mstring &name, ref<YIcon> icon, upath path):
22     DObject(app, name, icon), fPath(path)
23 {
24 }
25 
~DFile()26 DFile::~DFile() {
27 }
28 
open()29 void DFile::open() {
30     const char *args[] = { openCommand, fPath.string(), nullptr };
31     app()->runProgram(openCommand, args);
32 }
33 
DProgram(IApp * app,YSMListener * smActionListener,const mstring & name,ref<YIcon> icon,const bool restart,const char * wmclass,upath exe,YStringArray & args)34 DProgram::DProgram(
35     IApp *app,
36     YSMListener *smActionListener,
37     const mstring &name,
38     ref<YIcon> icon,
39     const bool restart,
40     const char *wmclass,
41     upath exe,
42     YStringArray &args)
43     : DObject(app, name, icon),
44     fRestart(restart),
45     fRes(newstr(wmclass)),
46     fPid(0),
47     fCmd(exe),
48     fArgs(args),
49     smActionListener(smActionListener)
50 {
51     if (fArgs.isEmpty() || fArgs.getString(fArgs.getCount() - 1))
52         fArgs.append(nullptr);
53 }
54 
~DProgram()55 DProgram::~DProgram() {
56     delete[] fRes;
57 }
58 
open()59 void DProgram::open() {
60     if (fRestart)
61         smActionListener->restartClient(fCmd.string(), fArgs.getCArray());
62     else if (fRes)
63         smActionListener->runOnce(fRes, &fPid, fCmd.string(), fArgs.getCArray());
64     else
65         app()->runProgram(fCmd.string(), fArgs.getCArray());
66 }
67 
newProgram(IApp * app,YSMListener * smActionListener,const char * name,ref<YIcon> icon,const bool restart,const char * wmclass,upath exe,YStringArray & args)68 DProgram *DProgram::newProgram(
69     IApp *app,
70     YSMListener *smActionListener,
71     const char *name,
72     ref<YIcon> icon,
73     const bool restart,
74     const char *wmclass,
75     upath exe,
76     YStringArray &args)
77 {
78     DProgram* program = nullptr;
79     if (exe != null) {
80         const char* exestr = exe.string();
81         MSG(("LOOKING FOR: %s\n", exestr));
82         csmart path(path_lookup(exestr));
83         if (path) {
84             program = new DProgram(app, smActionListener, name, icon,
85                                    restart, wmclass, upath(path), args);
86         } else {
87             MSG(("Program %s (%s) not found.", name, exestr));
88         }
89     }
90     return program;
91 }
92 
93 KProgramArrayType keyProgs;
94 
KProgram(const char * key,DProgram * prog,bool bIsDynSwitchMenuProg)95 KProgram::KProgram(const char *key, DProgram *prog, bool bIsDynSwitchMenuProg) :
96     fKey(NoSymbol),
97     fMod(0),
98     bIsDynSwitchMenu(bIsDynSwitchMenuProg),
99     fProg(prog),
100     pSwitchWindow(nullptr)
101 {
102     YConfig::parseKey(key, &fKey, &fMod);
103     keyProgs.append(this);
104 }
105 
106 class MenuProgSwitchItems: public ISwitchItems {
107     MenuProgMenu *menu;
108     int zTarget;
109 
110     KeySym key;
111     unsigned int mod;
112 
113 public:
MenuProgSwitchItems(DProgram * prog,KeySym key,unsigned keymod)114     MenuProgSwitchItems(DProgram* prog, KeySym key, unsigned keymod) :
115         ISwitchItems(), zTarget(0), key(key), mod(keymod) {
116         menu = new MenuProgMenu(wmapp, wmapp, nullptr /* no wmaction handling*/,
117                 "switch popup internal menu", prog->fCmd, prog->fArgs);
118     }
updateList()119     virtual void updateList() override {
120         menu->refresh();
121         zTarget = 0;
122     }
getCount()123     virtual int getCount() override {
124         return menu->itemCount();
125     }
isKey(KeySym k,unsigned int mod)126     virtual bool isKey(KeySym k, unsigned int mod) override {
127         return k == this->key && mod == this->mod;
128     }
modifiers()129     unsigned modifiers() override {
130         return mod;
131     }
setWMClass(char * wmclass)132     virtual void setWMClass(char* wmclass) override {
133         if (wmclass) free(wmclass); // unimplemented
134     }
getWMClass()135     virtual char* getWMClass() override {
136         return nullptr;
137     }
138 
139     // move the focused target up or down and return the new focused element
moveTarget(bool zdown)140     virtual void moveTarget(bool zdown) override {
141         int count = getCount();
142         zTarget += zdown ? 1 : -1;
143         if (zTarget >= count)
144             zTarget = 0;
145         if (zTarget < 0)
146             zTarget = max(0, count - 1);
147         // no further gimmicks
148     }
149     // move the focused target up or down and return the new focused element
setTarget(int where)150     virtual void setTarget(int where) override {
151         zTarget = clamp(where, 0, getCount() - 1);
152     }
153 
154     // set target cursor and implementation specific stuff in the beginning
begin(bool zdown)155     virtual void begin(bool zdown) override {
156         updateList();
157         zTarget = 0;
158         moveTarget(zdown);
159     }
160 
cancel()161     virtual void cancel() override {
162     }
accept()163     virtual void accept() override {
164         YMenuItem* item = menu->getItem(zTarget);
165         if (item) {
166             menu->actionPerformed(item->getAction(), 0);
167         }
168     }
169 
getActiveItem()170     virtual int getActiveItem() override {
171         return zTarget;
172     }
getTitle(int idx)173     virtual mstring getTitle(int idx) override {
174         return inrange(idx, 0, getCount() - 1) ?
175             menu->getItem(idx)->getName() : null;
176     }
getIcon(int idx)177     virtual ref<YIcon> getIcon(int idx) override {
178         return inrange(idx, 0, getCount() - 1) ?
179             menu->getItem(idx)->getIcon() : null;
180     }
181 };
182 
open(unsigned mods)183 void KProgram::open(unsigned mods) {
184     if (!fProg) return;
185 
186     if (bIsDynSwitchMenu) {
187         if (!pSwitchWindow) {
188             ISwitchItems* items = new MenuProgSwitchItems(fProg, fKey, fMod);
189             pSwitchWindow = new SwitchWindow(desktop, items, quickSwitchVertical);
190         }
191         pSwitchWindow->begin(true, mods);
192     }
193     else
194         fProg->open();
195 }
196 
MenuFileMenu(IApp * app,YSMListener * smActionListener,YActionListener * wmActionListener,mstring name,YWindow * parent)197 MenuFileMenu::MenuFileMenu(
198     IApp *app,
199     YSMListener *smActionListener,
200     YActionListener *wmActionListener,
201     mstring name,
202     YWindow *parent)
203     :
204     ObjectMenu(wmActionListener, parent),
205     MenuLoader(app, smActionListener, wmActionListener),
206     fName(name),
207     fModTime(0),
208     app(app)
209 {
210 }
211 
~MenuFileMenu()212 MenuFileMenu::~MenuFileMenu() {
213 }
214 
updatePopup()215 void MenuFileMenu::updatePopup() {
216     if (!autoReloadMenus && fPath != null)
217         return;
218 
219     upath np = app->findConfigFile(upath(fName));
220     bool rel = false;
221 
222 
223     if (fPath == null) {
224         fPath = np;
225         rel = true;
226     } else {
227         if (np == null || np.equals(fPath)) {
228             fPath = np;
229             rel = true;
230         } else
231             np = null;
232     }
233 
234     if (fPath == null) {
235         refresh();
236     } else {
237         struct stat sb;
238         if (stat(fPath.string(), &sb) != 0) {
239             fPath = null;
240             refresh();
241         } else if (sb.st_mtime > fModTime || rel) {
242             fModTime = sb.st_mtime;
243             refresh();
244         }
245     }
246 }
247 
refresh()248 void MenuFileMenu::refresh() {
249     removeAll();
250     if (fPath != null)
251         loadMenus(fPath, this);
252 }
253 
MenuProgMenu(IApp * app,YSMListener * smActionListener,YActionListener * wmActionListener,mstring name,upath command,YStringArray & args,long timeout,YWindow * parent)254 MenuProgMenu::MenuProgMenu(
255     IApp *app,
256     YSMListener *smActionListener,
257     YActionListener *wmActionListener,
258     mstring name,
259     upath command,
260     YStringArray &args,
261     long timeout,
262     YWindow *parent)
263     :
264     ObjectMenu(wmActionListener, parent),
265     MenuLoader(app, smActionListener, wmActionListener),
266     fName(name),
267     fCommand(command),
268     fArgs(args),
269     fModTime(0),
270     fTimeout(timeout)
271 {
272 }
273 
~MenuProgMenu()274 MenuProgMenu::~MenuProgMenu() {
275 }
276 
updatePopup()277 void MenuProgMenu::updatePopup() {
278     time_t now = seconds();
279     if (fModTime == 0 || (0 < fTimeout && now >= fModTime + fTimeout)) {
280         refresh();
281         fModTime = now;
282     }
283 }
284 
refresh()285 void MenuProgMenu::refresh()
286 {
287     removeAll();
288     if (fCommand != null)
289         progMenus(fCommand.string(), fArgs.getCArray(), this);
290 }
291 
StartMenu(IApp * app,YSMListener * smActionListener,YActionListener * wmActionListener,const char * name,YWindow * parent)292 StartMenu::StartMenu(
293     IApp *app,
294     YSMListener *smActionListener,
295     YActionListener *wmActionListener,
296     const char *name,
297     YWindow *parent)
298     : MenuFileMenu(app, smActionListener, wmActionListener, name, parent),
299     smActionListener(smActionListener),
300     wmActionListener(wmActionListener)
301 {
302 }
303 
handleKey(const XKeyEvent & key)304 bool StartMenu::handleKey(const XKeyEvent &key) {
305     // If meta key, close the popup
306     if (key.type == KeyPress) {
307         KeySym k = keyCodeToKeySym(key.keycode);
308         int m = KEY_MODMASK(key.state);
309 
310         if ((k == xapp->Win_L ||
311              k == XK_Multi_key ||
312              k == xapp->Win_R) && m == 0)
313         {
314             cancelPopup();
315             return true;
316         }
317     }
318     return MenuFileMenu::handleKey(key);
319 }
320 
updatePopup()321 void StartMenu::updatePopup() {
322     MenuFileMenu::updatePopup();
323 }
324 
FocusMenu()325 FocusMenu::FocusMenu() {
326     struct FocusModelNameAction {
327         FocusModel mode;
328         const char *name;
329         YAction action;
330     } foci[] = {
331         { FocusClick, _("_Click to focus"), actionFocusClickToFocus },
332         { FocusExplicit, _("_Explicit focus"), actionFocusExplicit },
333         { FocusSloppy, _("_Sloppy mouse focus"), actionFocusMouseSloppy },
334         { FocusStrict, _("S_trict mouse focus"), actionFocusMouseStrict },
335         { FocusQuiet, _("_Quiet sloppy focus"), actionFocusQuietSloppy },
336         { FocusCustom, _("Custo_m"), actionFocusCustom },
337     };
338     for (size_t k = 0; k < ACOUNT(foci); ++k) {
339         YMenuItem *item = addItem(foci[k].name, -2, null, foci[k].action);
340         if (wmapp->getFocusMode() == foci[k].mode) {
341             item->setEnabled(false);
342             item->setChecked(true);
343         }
344     }
345 }
346 
HelpMenu(IApp * app,YSMListener * smActionListener,YActionListener * wmActionListener)347 HelpMenu::HelpMenu(
348     IApp *app,
349     YSMListener *smActionListener,
350     YActionListener *wmActionListener)
351     : ObjectMenu(wmActionListener)
352 {
353     struct HelpMenuItem {
354         const char *name;
355         const char *menu;
356         const char *clas;
357     } help[] = {
358         { ICEHELPIDX, _("_Manual"), "browser.Manual" },
359         { "icewm.1.html", _("_Icewm(1)"), "browser.IceWM" },
360         { "icewmbg.1.html", _("Icewm_Bg(1)"), "browser.IcewmBg" },
361         { "icesound.1.html", _("Ice_Sound(1)"), "browser.IceSound" },
362     };
363     for (size_t k = 0; k < ACOUNT(help); ++k) {
364         YStringArray args(3);
365         args.append(ICEHELPEXE);
366         if (k == 0) {
367             args.append(help[k].name);
368         } else {
369             upath path = upath(ICEHELPIDX).parent() + help[k].name;
370             args.append(path.string());
371         }
372         args.append(nullptr);
373 
374         DProgram *prog = DProgram::newProgram(
375             app,
376             smActionListener,
377             help[k].menu,
378             null,
379             false,
380             help[k].clas,
381             ICEHELPEXE,
382             args);
383 
384         if (prog)
385             addObject(prog, "help");
386     }
387 }
388 
refresh()389 void StartMenu::refresh() {
390     MenuFileMenu::refresh();
391 
392     addSeparator();
393 
394 /// TODO #warning "make this into a menuprog (ala gnome.cc), and use mime"
395     if (nonempty(openCommand)) {
396         upath path[] = { upath::root(), YApplication::getHomeDir() };
397         ObjectMenu* sub;
398         for (const upath& p : path) {
399             sub = new BrowseMenu(app, smActionListener, wmActionListener, p);
400             DFile *file = new DFile(app, p, null, p);
401             addObject(file, "folder", sub, false);
402         }
403         addSeparator();
404     }
405 
406     if (showPrograms) {
407         ObjectMenu *programs = new MenuFileMenu(app, smActionListener,
408                                     wmActionListener, "programs", nullptr);
409         ///    if (programs->itemCount() > 0)
410         addSubmenu(_("Programs"), 0, programs, "programs");
411     }
412 
413     if (showRun && nonempty(runDlgCommand)) {
414         addItem(_("_Run..."), -2, null, actionRun, "run");
415     }
416 
417     if (showWindowList) {
418         addItem(_("_Windows"), -2, actionWindowList, windowListMenu, "windows");
419     }
420 
421     YMenu* settings = new YMenu();
422 
423     if (!showTaskBar && showAbout) {
424         settings->addItem(_("_About"), -2, actionAbout, nullptr, "about");
425     }
426 
427     if (showHelp) {
428         HelpMenu* help = new HelpMenu(app, smActionListener, wmActionListener);
429         settings->addSubmenu(_("_Help"), -2, help, "help");
430     }
431 
432     if (showFocusModeMenu) {
433         FocusMenu *focus = new FocusMenu();
434         settings->addSubmenu(_("_Focus"), -2, focus, "focus");
435     }
436 
437     if (showSettingsMenu) {
438         PrefsMenu *prefs = new PrefsMenu();
439         settings->addSubmenu(_("_Preferences"), -2, prefs, "pref");
440     }
441 
442     if (showThemesMenu) {
443         YMenu *themes = new ThemesMenu(app, smActionListener, wmActionListener);
444         settings->addSubmenu(_("_Themes"), -2, themes, "themes");
445     }
446 
447     if (settings->itemCount()) {
448         addSubmenu(_("Se_ttings"), -2, settings, "settings");
449     } else {
450         delete settings;
451     }
452 
453     if (showLogoutMenu) {
454         addSeparator();
455         if (showLogoutSubMenu)
456             addItem(_("_Logout..."), -2, actionLogout, logoutMenu, "logout");
457         else
458             addItem(_("_Logout..."), -2, null, actionLogout, "logout");
459     }
460 }
461 
462 // vim: set sw=4 ts=4 et:
463