Showing posts with label Vivaldi. Show all posts
Showing posts with label Vivaldi. Show all posts

Tuesday, July 21, 2026

Modeling Browser History in Emacs

If there were an underappreciated web browser feature, it would be this: history.

On many browsers, history is invoked with C-h (Ctrl+h). Vivaldi shows history as a calendar. The user can choose from time intervals of Day, Week or Month and search for a keyword within that time interval.1

I tend to write about a topic after I've forgotten where I read about it. Suppose I recall reading about "nslookup" but can't remember when or where. Simple. Vivaldi remembered that I visited a page with "nslookup" on Windows Central on July 3 at 21:26. Being able to find such references helps.

History helped me track my hours when my employer required bi-weekly timesheets. I would combine browser history with Org's clock report to get a result I was actually proud to submit.

I still track my time with Org, which stores start and stop times in a drawer called LOGBOOK. But I tend to forget to start or stop the clock, so the times are unreliable. Sometimes a file's timestamp can help me reconstruct the LOGBOOK. But at times like these I'd wish that Emacs had a history feature.

Actually Emacs does have a few history-like features. One example is savehist-mode,2 which "save[s] the values of minibuffer history variables." Unfortunately, timestamps are not included.

So I configured Emacs to write a timestamp to the *Messages* buffer every time it loads or saves a file. This frees me from switching to a file manager to check a file's timestamp. Using Customize, I added functions to find-file-hook and after-save-hook:3

(find-file-hook
 '(lambda nil
     (message "%s: Loaded %s"
              (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
              (buffer-file-name))))
(after-save-hook
 '(lambda nil
    (message "%s: Saved %s"
             (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))
             (buffer-file-name))))

Whenever I realize I'm not clocked in, I visit the *Messages* buffer and look for the first occurrence of something like this:

2026-07-20 12:32:33: Saved c:/Users/RayZ/AppData/Roaming/HOME/blog.org

Then I clock in and change the start time to 12:33.4

Similarly, if I've been away from the computer and realize I'm still clocked in, I'll look for the last occurrence of the message, clock out, and change the time accordingly.

The *Messages* buffer is temporary; timestamps from an earlier Emacs session are lost. But this simple system does what I need.

What methods do you use to track project time?


1 The user also can view history as a simple list in reverse chronological order. And the search can span the entire history, if desired.

2 https://doc.endlessparentheses.com/Fun/savehist-mode.html

3 Note that these expressions are arguments to custom-set-variables.

4 Timesheets required time entries in units of hours rounded to one decimal. A tenth hour is 6 minutes. So I configured Org to round clock times to 3 minutes by setting org-clock-rounding-minutes to 3. This affords more granularity. With several clock entries per heading, an even number of 0:03 clock entries would produce the desired tenth hour accuracy.

Wednesday, July 8, 2026

Configure Web Browser to be More Emacs-like

Emacs, LibreOffice Writer and Vivaldi are the productivity tools I use most often. So I like to incorporate a feature from one into the others. One example is the ability to initiate a search from selected text. I added this feature to LibreOffice as described here. Another nice feature is the ability to switch between the current buffer and the next (or previous) buffer with C-<TAB> and C-S-<TAB> that I added to Emacs1.

But I've also configured Vivaldi with a useful Emacs feature. Now it responds to C-S-k by invoking "Close tabs to the Right." Emacs devotees know that C-k is bound to kill-line, which "Kill[s] the rest of the current line...."2 If you imagine that the row of tabs is a line of text, it's natural to expect C-k (or C-S-k) would remove all the tabs to the right of the current tab.

Why did I choose the key combination C-S-k instead of C-k? I think at the time C-k performed another function. Or perhaps I felt that it was more appropriate to use two modifier keys for such a significant operation. I rarely need to delete tabs to the left; if I did, I'd bind C-M-S-k to "Close tabs to the Left."

You might utter the following complaint, "But C-S-k doesn't work for Gmail; it brings up a virtual keyboard instead!" Correct. So while we're on the topic of Vivaldi key configurations, I'd like to point out that it's possible to tell Vivaldi to ignore a website's key assignment, allowing the key to pass to Vivaldi. Just search for the setting "Browser Priority Shortcuts," click on one of the existing key combinations and press C-S-k to add it to the others.

I rely on the web browser's tab history a great deal, too. I'm working on implementing something vaguely similar for Emacs."


1 To get the C-<TAB> and C-S-<TAB> to switch buffers, just add this to your init file:

  (keymap-global-set "C-<tab>" 'next-buffer)
  (keymap-global-set "C-S-<tab>" 'previous-buffer)

2 https://www.gnu.org/software/emacs/manual/html_node/emacs/Killing-by-Lines.html

Wednesday, June 24, 2026

Add Web Search to LibreOffice Writer

Introduction

In Vivaldi1 I can right click on a word or selected phrase and search for it. But in LibreOffice Writer, I'd have to copy the selection to my clipboard, switch to the browser, paste it into the search bar and then press Enter. I longed to have a generic search capability for Writer. So I created it.

Well, first I searched for it, and I found a macro that was designed to do something similar2. It provided the Basic code I needed to read the selected text.3 It had to be modified somewhat to work on my computer, but it was a tremendous help.

Then I built on it. The collection of macros that I wrote allows a user to perform the search using several search engines. And it's amenable to configuration in the Context Menu.

Workflow Summary -- Search the Internet for selected text
  • Select some text.
  • Right click on it.
  • Search the Internet for it.
  • Use any one of these these preconfigured search engines:
    • DuckDuckGo
    • Google
    • LinkedIn
    • Wikipedia
    • Open Library
    • The Free Dictionary
    • And you can add your own!

Detail

Web Search is a LibreOffice Basic module that contains the macros needed to perform an Internet search of selected text.

Program Flow

A unique macro is dedicated to each search engine. The user selects text and then invokes the macro that corresponds to the desired search engine.

The invoked macro sets an index that points to the search engine URL in an array. Then it invokes the Main subroutine.

Main calls an Initialize routine, which loads the path to the web browser executable and all the Search Engine URLs.

Main performs two checks. First it verifies that the search engine index is within the bounds of the array of search engine URLs. The out-of-the-box state is that the index should be in the range 0 to 5.

The second check is that Main verifies that text has been selected in the Writer document.

If and only if the previous two checks are successful, then Main calls the InvokeSearch routine; the search engine URL and the search terms are its arguments. If either check fails, Main and the invoked macro terminate; no action is performed.

InvokeSearch replaces a token in the search engine URL with the search terms. Then it calls the system shell, which opens the web browser with the conditioned search engine URL as an argument. The shell process is asynchronous.

The web browser takes focus and displays search results.

Finally, the three procedures (two subroutines and a function) are removed from the call stack, and the global variables are cleared.

Context Menu Setup

The LibreOffice Context Menu can be configured to provide easy access to each Search Engine macro. The user can right click on selected text, choose "Search on Web...," and then choose the submenu item that corresponds to the desired search engine. In Figure 1 below, the DuckDuckGo search engine is selected. Thus each Web Search submenu item is assigned a unique macro.

Figure 1: The SearchWeb Context Menu


Configure the context menu as shown in Figure 2, below. Access this dialog window by selecting Tools | Customize on the menu and then click the Context Menu tab.

Figure 2: Customizing the Context Menu


The corresponding XML content is contained in the text.xml file, which is located here in the user's profile:

AppData\Roaming\LibreOffice\4\user\config\soffice.cfg\modules\swriter\popupmenu\

The first 20 lines of text.xml are provided in the listing below. Note that the menu items for Open Library and The Free Dictionary are wrapped.

It may be easier to edit this file than to use the Customize form that's shown above.

Contents of text.xml

<?xml version="1.0" encoding="UTF-8"?>
-
<menu:menupopup xmlns:menu="http://openoffice.org/2001/menu">
  -
  <menu:menu menu:label="Search on Web..." menu:id="Search on Web...">
    -
    <menu:menupopup>
	<menu:menuitem menu:label="D DuckDuckGo" menu:id="vnd.sun.star.script:Standard.SearchWeb.SEDuckDuckGo?language=Basic&location=application"/>
	<menu:menuitem menu:label="G Google" menu:id="vnd.sun.star.script:Standard.SearchWeb.SEGoogle?language=Basic&location=application"/>
	<menu:menuitem menu:label="L LinkedIn" menu:id="vnd.sun.star.script:Standard.SearchWeb.SELinkedIn?language=Basic&location=application"/>
	<menu:menuitem menu:label="O Open Library" menu:id="vnd.sun.star.script:Standard.SearchWeb.SEOpenLibrary?language=Basic&location=application"/>
	<menu:menuitem menu:label="F The Free Dictionary" menu:id="vnd.sun.star.script:Standard.SearchWeb.SETheFreeDictionary?language=Basic&location=application"/>
	<menu:menuitem menu:label="W Wikipedia" menu:id="vnd.sun.star.script:Standard.SearchWeb.SEWikipedia?language=Basic&location=application"/>
    </menu:menupopup>
  </menu:menu>
  <menu:menuitem menu:id=".uno:NoBreak"/>
  <menu:menuseparator/>
  <menu:menuitem menu:id=".uno:Cut"/>
  <menu:menuitem menu:id=".uno:Copy"/>
  <menu:menuitem menu:id=".uno:Paste"/>


1 Vivaldi, https://vivaldi.com/desktop/
2 Ratslinger's post in this thread on the LibreOffice help forum provided a solution way back in September 2018 https://ask.libreoffice.org/t/link-to-browser-in-writer/35943/6
3 Here's a function that returns the selected text; words in a phrase are separated+with+plus+signs+instead+of+spaces:
    Private Function GetSelection() As String
      ' Returns Selected Text
      ' 2025-12-22 Created
      Dim oSelections, oFirstSelection As Object
      oSelections = ThisComponent.CurrentSelection
      oFirstSelection = oSelections.getByIndex(0)
      strTemp = oFirstSelection.String
      If strTemp <> "" Then
        GetSelection = Replace(strTemp, " ", "+")
      End If
    End Function

Thursday, June 18, 2026

Add QR Code To Calendar Event Description

It can be convenient to include a QR code in the description of a calendar event. However, the three online calendar apps that I use1 do not provide a way to add images to the event descriptions. Here's a workaround that works with Google Calendar (so far), but it requires a QR code generator that outputs text (extended ASCII) as opposed to PNG, JPG or SVG.2

I invoke the generator from the CLI, entering qr.exe https://www.fsf.org --ascii (where https://www.fsf.org is the content to be encoded). It outputs directly to the CLI, as shown in Figure 1. Why not redirect the output to a text file, such as QRCode.txt? I tried, but the resulting file is in PNG format, not text.

Figure 1 -- Screenshot of the CLI that
  shows the command and its output

Since text output cannot be redirected to a file (at this time), I must do this manually by selecting and copying the text from the CLI to the clipboard. And I paste it into an HTML file, actually, and enclose it in a pre tag to ensure that it's rendered in a monospace font family, as shown in the listing below.

  <pre style="font-family: monospace, monospace; font-size: small;">
    █▀▀▀▀▀█ ▄▀ ▀▀█  ▄ █▀▀▀▀▀█    
    █ ███ █ █▄  █▄ ▄█ █ ███ █    
    █ ▀▀▀ █  ▄▀  ▄▄ ▀ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ ▀▄█ █▄█ ▀▀▀▀▀▀▀    
    █ ▄  ▄▀ █▄▀  ▄▄▄▄█▀▄▄███     
    ▄▀ ▄▀ ▀▄ █ █▀█▀█▄█▀█▀█ ▀█    
    ███▀ ▀▀  ▀  ▀▀ ██▄▀▄   ▄▀    
    █ █▀▀ ▀▀▀ ▄█▀  ███▀█▀█▄▀█    
    ▀ ▀▀▀ ▀ █ ▄▀▄▄▄██▀▀▀█ ▀      
    █▀▀▀▀▀█  ▀▀▀▄▄ ▄█ ▀ █  ▄▀    
    █ ███ █  ▄▀ ▀ █▀▀▀█▀▀ ▀▄█    
    █ ▀▀▀ █  ▀▄█▀ ▄ ▄▄ ▄▄▀▀ █    
    ▀▀▀▀▀▀▀ ▀▀▀  ▀▀ ▀▀   ▀  ▀    
  </pre>

I open qrcode.html in my web browser, as shown in Figure 2.

Figure 2 -- qrcode.html rendered in a Vivaldi browser window

I select the rendered HTML and copy it to the clipboard. Then I paste it into the event description, as shown in Figure 3.

Figure 3 -- Screenshot of
  the Google calendar event description

In real life I'd encode an access code that allows me to enter the meeting venue. The FSF URL is used here as an example.

How is this related to Emacs? I never claimed that this blog is devoted entirely to Emacs. But if it helps, I wrote the HTML for this post with Emacs, as well as the qrcode.html file.

I'm just curious if anyone else finds this useful or if you have other workarounds to go beyond the limitations of pedestrian apps.


1The calendar apps are from Google, Yahoo, Proton.
2QRCode is a Python module that provides an executable, qr.exe. It can be obtained from PyPi: https://pypi.org/project/qrcode/8.2/.